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,81 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # poetry's editor hook, written by `bin/rails g poetry:editor`: runs
5
+ # `poetry check` on the app template an agent just edited and hands the
6
+ # findings straight back to the agent, so the check runs after EVERY edit
7
+ # instead of when the agent remembers to. Stdlib only; the check itself runs
8
+ # through bin/rails, so it sees the same registry, icon set, and charts the
9
+ # rake task sees.
10
+ #
11
+ # Two hook dialects, told apart by the payload:
12
+ # Claude Code (PostToolUse): errors -> exit 2 with the report on stderr (fed
13
+ # back to the agent); warnings -> exit 0 with additionalContext.
14
+ # Cursor (postToolUse): any findings -> exit 0 with additional_context.
15
+ # Anything that is not an app template exits 0 without booting Rails; a check
16
+ # that cannot run at all exits 1, so the loop fails open, never closed.
17
+
18
+ require "json"
19
+ require "open3"
20
+
21
+ payload = begin
22
+ JSON.parse($stdin.read)
23
+ rescue JSON::ParserError
24
+ {}
25
+ end
26
+ payload = {} unless payload.is_a?(Hash)
27
+ tool_input = payload["tool_input"].is_a?(Hash) ? payload["tool_input"] : {}
28
+ edited = tool_input["file_path"] || tool_input["path"] || payload["file_path"]
29
+ exit 0 unless edited.is_a?(String) && !edited.empty?
30
+
31
+ project = ENV["CLAUDE_PROJECT_DIR"] || Array(payload["workspace_roots"]).first || payload["cwd"] || Dir.pwd
32
+ root = File.expand_path(project)
33
+ full = File.expand_path(edited, root)
34
+ exit 0 unless full.start_with?("#{root}/app/") && full.end_with?(".html.erb") && File.file?(full)
35
+ template = full.delete_prefix("#{root}/")
36
+
37
+ cursor = payload.key?("conversation_id") || payload["hook_event_name"] == "postToolUse"
38
+
39
+ begin
40
+ stdout, stderr, status = Open3.capture3({ "POETRY_CHECK_JSON" => "1" }, "bin/rails", "poetry:check[#{template}]",
41
+ chdir: root)
42
+ rescue SystemCallError => e
43
+ warn "poetry check hook: could not run bin/rails (#{e.message})"
44
+ exit 1
45
+ end
46
+ findings = begin
47
+ JSON.parse(stdout)
48
+ rescue JSON::ParserError
49
+ nil
50
+ end
51
+ unless findings.is_a?(Array)
52
+ warn "poetry check hook: poetry:check did not run (exit #{status.exitstatus})\n#{stderr.lines.last(10).join}"
53
+ exit 1
54
+ end
55
+ if findings.empty?
56
+ puts "{}" if cursor
57
+ exit 0
58
+ end
59
+
60
+ errors = findings.count { |finding| finding["severity"] == "error" }
61
+ warnings = findings.size - errors
62
+ lines = findings.map do |finding|
63
+ hint = finding["suggestion"] ? " (did you mean #{finding["suggestion"]}?)" : ""
64
+ "- [#{finding["severity"]}] line #{finding["line"]}: #{finding["message"]}#{hint}"
65
+ end
66
+ headline = if errors.positive?
67
+ "poetry check: #{errors} error(s), #{warnings} warning(s) in #{template} - " \
68
+ "an error raises or misrenders at render time; fix it, then re-run poetry check"
69
+ else
70
+ "poetry check: #{warnings} warning(s) in #{template} - advisory; fix unless the deviation is deliberate"
71
+ end
72
+ report = [headline, *lines].join("\n")
73
+
74
+ if cursor
75
+ puts JSON.generate("additional_context" => report)
76
+ elsif errors.positive?
77
+ warn report
78
+ exit 2
79
+ else
80
+ puts JSON.generate("hookSpecificOutput" => { "hookEventName" => "PostToolUse", "additionalContext" => report })
81
+ end
@@ -0,0 +1,364 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require_relative "../agents_section"
5
+ require_relative "../skills_section"
6
+
7
+ module Poetry
8
+ # `rails g poetry:install` - wires poetry into a host app:
9
+ #
10
+ # app/assets/tailwind/poetry/tokens.css the design tokens (:root + .dark)
11
+ # app/assets/tailwind/poetry/theme.css the Tailwind v4 @theme mapping
12
+ # app/assets/tailwind/poetry/animate.css the vendored animation
13
+ # utility layer (Rails hosts
14
+ # have no npm)
15
+ # app/assets/tailwind/poetry/safelist.txt every dictionary + template class
16
+ # (so the host build never purges
17
+ # classes resolved in Ruby)
18
+ # config/initializers/poetry.rb commented configuration
19
+ # config/poetry_components.yml the copy-in manifest (empty)
20
+ #
21
+ # plus idempotent @import/@source injection into the host's Tailwind entry
22
+ # (append-unless-present - re-running install is always safe).
23
+ #
24
+ # `--charts` additionally wires poetry-charts (the gem must already be in
25
+ # the bundle - its engine merges the @poetry/charts importmap pins and the
26
+ # safelist pass picks the chart dictionaries up on its own; what a host
27
+ # still needs by hand is the motion stylesheet in the Tailwind entry and
28
+ # the Stimulus registration, and that is exactly what the flag does).
29
+ #
30
+ # @example
31
+ # bin/rails g poetry:install --theme vega
32
+ class InstallGenerator < Rails::Generators::Base
33
+ include Generators::AgentsSection
34
+ include Generators::SkillsSection
35
+
36
+ # The host's Tailwind entry stylesheet (import/@source injection).
37
+ TAILWIND_ENTRY = "app/assets/tailwind/application.css"
38
+ # The theme slot: --theme swaps its CONTENT, the filename stays put.
39
+ STYLE_SLOT = "app/assets/tailwind/poetry/style-default.css"
40
+ # Every shipped fragment opens with `/* poetry <name> theme` - the sniff
41
+ # reads the slot's first line back so a plain re-run keeps the theme.
42
+ THEME_HEADER = %r{\A/\* poetry ([a-z0-9-]+) theme\b}
43
+
44
+ class_option :charts, type: :boolean, default: false,
45
+ desc: %(Also wire poetry-charts (requires gem "poetry-charts" in the bundle))
46
+
47
+ # Install-time theme selection. Every themes/<name>.css fragment is
48
+ # a complete visual theme; the chosen one fills the style-default.css
49
+ # slot (the SLOT filename never changes - that keeps ENTRY_LINES
50
+ # idempotent and makes switching themes a re-run with a different
51
+ # --theme, overwriting in place). One theme per build; the multi-theme
52
+ # .style-<name> wrapper arrives with the docs switcher, not here.
53
+ #
54
+ # The upgrade-path contract: the default is nil, not "default" -
55
+ # a plain re-run sniffs the theme already in the slot and keeps it, so
56
+ # upgrading (bundle update + re-run) never swaps an app's design. An
57
+ # explicit --theme always wins; only a first install falls to "default".
58
+ class_option :theme, type: :string, default: nil,
59
+ desc: "Visual theme fragment to install (a themes/<name>.css shipped by poetry-ui); " \
60
+ "defaults to the theme already installed, or \"default\" on a first install"
61
+
62
+ # Injected line by line (not as one block) so a re-run after an upgrade
63
+ # appends any line a previous poetry version didn't know about.
64
+ ENTRY_LINES = [
65
+ %(@import "./poetry/tokens.css";),
66
+ %(@import "./poetry/theme.css";),
67
+ %(@import "./poetry/animate.css";),
68
+ %(@import "./poetry/utilities.css";),
69
+ %(@import "./poetry/aliases.css";),
70
+ %(@import "./poetry/style-default.css" layer(base);),
71
+ %(@import "./poetry/base.css";),
72
+ %(@import "./poetry/typeset.css";),
73
+ %(@source "./poetry/safelist.txt";)
74
+ ].freeze
75
+
76
+ # The base layer (upstream's init writes the same defaults into the
77
+ # host stylesheet):
78
+ # without it body/border/outline don't ride the tokens and dark mode
79
+ # only flips the components. Seeded once,
80
+ # user-owned - re-install never overwrites.
81
+ BASE_CSS = <<~CSS
82
+ /* poetry base layer (source-parity defaults) - yours to edit. */
83
+ @layer base {
84
+ * {
85
+ border-color: var(--border);
86
+ outline-color: color-mix(in oklab, var(--ring) 50%, transparent);
87
+ }
88
+ body {
89
+ background-color: var(--background);
90
+ color: var(--foreground);
91
+ }
92
+ }
93
+ CSS
94
+
95
+ desc "Install poetry: tokens, Tailwind theme, safelist, initializer, and the component manifest"
96
+
97
+ # Fails fast BEFORE any file lands: --charts against a bundle without
98
+ # the gem would otherwise half-install (css copied, dead registration).
99
+ # @api private
100
+ def verify_charts_gem
101
+ return if !options[:charts] || charts_available?
102
+
103
+ raise Thor::Error, "--charts needs poetry-charts in the bundle - add " \
104
+ '`gem "poetry-charts"` to the Gemfile, bundle, and re-run'
105
+ end
106
+
107
+ # Same fail-fast for --theme: an unknown name (or one poetry-charts
108
+ # doesn't ship when --charts is on) must not half-install.
109
+ # @api private
110
+ def verify_theme_choice
111
+ unless ui_theme_path.exist?
112
+ available = Dir[Poetry::Ui.root.join("themes/*.css").to_s].map { |f| File.basename(f, ".css") }.sort
113
+ raise Thor::Error, "unknown poetry theme #{resolved_theme.inspect} - poetry-ui ships: #{available.join(", ")}"
114
+ end
115
+
116
+ return if !options[:charts] || !charts_available? || charts_theme_path.exist?
117
+
118
+ raise Thor::Error, "poetry-charts does not ship theme #{resolved_theme.inspect} - " \
119
+ "every installed poetry gem must provide themes/#{resolved_theme}.css"
120
+ end
121
+
122
+ # Step: copies tokens, the Tailwind theme mapping, the vendored css,
123
+ # and the theme slot.
124
+ # @api private
125
+ def copy_tokens_and_theme
126
+ create_file "app/assets/tailwind/poetry/tokens.css",
127
+ Poetry::Core.root.join("tokens/tokens.css").read, force: true
128
+ create_file "app/assets/tailwind/poetry/theme.css",
129
+ Poetry::Core.root.join("tokens/tailwind-theme.css").read, force: true
130
+ create_file "app/assets/tailwind/poetry/animate.css",
131
+ Poetry::Core.root.join("vendor/tw-animate-css/tw-animate.css").read, force: true
132
+ # The upstream first-party utility layer (shimmer / scroll-fade
133
+ # families + the chat-set keyframes), vendored verbatim at a pinned
134
+ # SHA - provenance in THIRD_PARTY_NOTICES.md (host filename stays
135
+ # utilities.css).
136
+ create_file "app/assets/tailwind/poetry/utilities.css",
137
+ Poetry::Core.root.join("vendor/shadcn-tailwind/tailwind.css").read, force: true
138
+ create_file "app/assets/tailwind/poetry/aliases.css",
139
+ Poetry::Core.root.join("tokens/aliases.css").read, force: true
140
+ # The cn-* theme layer: the named-class design source, imported
141
+ # layer(base) so host utilities always win. Vendored like tokens
142
+ # (force) - a host restyles by overriding .cn-* rules in its OWN css
143
+ # (any utilities-layer or unlayered rule beats layer(base)), never by
144
+ # editing this file, so theme updates keep flowing on re-install.
145
+ # --theme swaps the CONTENT; the slot filename stays put.
146
+ create_file "app/assets/tailwind/poetry/style-default.css",
147
+ ui_theme_path.read, force: true
148
+ create_file "app/assets/tailwind/poetry/base.css", BASE_CSS, skip: true
149
+ # poetry/typeset (the vendored prose-styling port - provenance in
150
+ # THIRD_PARTY_NOTICES.md): prose styling for
151
+ # rendered markdown. App-OWNED like base.css (skip, never force) -
152
+ # the whole point of the artifact is that the file is yours to tune.
153
+ create_file "app/assets/tailwind/poetry/typeset.css",
154
+ Poetry::Ui.root.join("typeset/typeset.css").read, skip: true
155
+ end
156
+
157
+ # Step: writes the dictionary + template-class safelist.
158
+ # @api private
159
+ def generate_safelist
160
+ # Style.descendants is empty until the component classes load - under
161
+ # `rails g` nothing has autoloaded them (the fresh-app proof caught
162
+ # this; the dummy suite masked it by eager-loading first).
163
+ Rails.application.eager_load!
164
+ styles = Poetry::Core::Style.descendants.select(&:name)
165
+ # Template-static classes (sr-only, animate-spin) come from the
166
+ # COMMITTED list - herb-extracted and drift-gated in poetry's CI,
167
+ # never required in a host (the browser pass caught the live-scan
168
+ # skip purging them).
169
+ safelist = Poetry::Core::CSS::Safelist.new(style_classes: styles,
170
+ template_classes: Poetry::Ui.template_classes)
171
+ create_file "app/assets/tailwind/poetry/safelist.txt", safelist.text, force: true
172
+ end
173
+
174
+ # Step: injects the poetry @import/@source lines into the host's
175
+ # Tailwind entry.
176
+ # @api private
177
+ def wire_tailwind_entry
178
+ unless File.exist?(File.join(destination_root, TAILWIND_ENTRY))
179
+ create_file TAILWIND_ENTRY, %(@import "tailwindcss";\n)
180
+ end
181
+ ENTRY_LINES.each { |line| inject_unless_present(TAILWIND_ENTRY, line) }
182
+ end
183
+
184
+ # Step: writes the commented configuration initializer.
185
+ # @api private
186
+ def create_initializer
187
+ create_file "config/initializers/poetry.rb", <<~RUBY, skip: true
188
+ # frozen_string_literal: true
189
+
190
+ # poetry configuration. Defaults shown commented.
191
+ #
192
+ # Poetry::Core::Config.current.css_mode = :tailwind # or :bem (bring your own CSS)
193
+ # Poetry::Core::Config.current.icon_library = :lucide
194
+ RUBY
195
+ end
196
+
197
+ # Step: writes the empty copy-in manifest.
198
+ # @api private
199
+ def create_manifest
200
+ create_file "config/poetry_components.yml", "components: {}\n", skip: true
201
+ end
202
+
203
+ # Pins alone don't register controllers: without this call every poetry
204
+ # controller is dead JS (the browser pass caught the dialog trigger
205
+ # doing nothing in a fresh host).
206
+ # @api private
207
+ def register_controllers
208
+ index = "app/javascript/controllers/index.js"
209
+ unless File.exist?(File.join(destination_root, index))
210
+ say_status :note, "no #{index} - register poetry's controllers yourself: " \
211
+ "registerPoetryControllers(application) from \"@poetry/controllers\" " \
212
+ "(and registerPoetryAgent(application) from \"@poetry/agent\" " \
213
+ "when poetry-agent is bundled)", :yellow
214
+ return
215
+ end
216
+
217
+ inject_unless_present(index, <<~JS.strip)
218
+ import { registerPoetryControllers } from "@poetry/controllers"
219
+ registerPoetryControllers(application)
220
+ JS
221
+ register_agent_runtime(index)
222
+ end
223
+
224
+ # --charts: the two host-side wires the charts engine cannot do itself.
225
+ # The motion stylesheet is COPIED (tailwindcss-rails compiles standalone;
226
+ # a gem-path @import would not resolve) - vendored artifact, force like
227
+ # tokens; the entry @import and the Stimulus registration ride the same
228
+ # idempotent primitives as the core wiring.
229
+ # @api private
230
+ def wire_charts
231
+ return unless options[:charts]
232
+
233
+ create_file "app/assets/tailwind/poetry/charts.css",
234
+ Poetry::Charts.root.join("app/assets/stylesheets/poetry-charts.css").read,
235
+ force: true
236
+ inject_unless_present(TAILWIND_ENTRY, %(@import "./poetry/charts.css";))
237
+ # The charts cn-* theme fragment - vendored like style-default,
238
+ # same --theme selection.
239
+ create_file "app/assets/tailwind/poetry/style-charts.css",
240
+ charts_theme_path.read, force: true
241
+ inject_unless_present(TAILWIND_ENTRY, %(@import "./poetry/style-charts.css" layer(base);))
242
+
243
+ index = "app/javascript/controllers/index.js"
244
+ unless File.exist?(File.join(destination_root, index))
245
+ say_status :note, "no #{index} - register the chart controllers yourself: " \
246
+ "registerPoetryChartsControllers(application) from \"@poetry/charts\"", :yellow
247
+ return
248
+ end
249
+
250
+ inject_unless_present(index, <<~JS.strip)
251
+ import { registerPoetryChartsControllers } from "@poetry/charts"
252
+ registerPoetryChartsControllers(application)
253
+ JS
254
+ end
255
+
256
+ # The llms.txt / llms-full.txt agent docs are engine routes - without
257
+ # the mount they are unreachable (the fresh-app proof caught this).
258
+ # @api private
259
+ def mount_engine
260
+ routes = File.join(destination_root, "config/routes.rb")
261
+ return unless File.exist?(routes)
262
+ return if File.read(routes).include?("Poetry::Ui::Engine")
263
+
264
+ route %(mount Poetry::Ui::Engine => "/poetry" # llms.txt + llms-full.txt (agent-facing docs))
265
+ end
266
+
267
+ # The AGENTS.md pointer section - marker-bounded so a re-run
268
+ # refreshes it in place. Standalone refresh: `rails g poetry:agents`.
269
+ # @api private
270
+ def write_agents_md
271
+ apply_agents_section
272
+ end
273
+
274
+ # The Claude Code skills - part of the standard
275
+ # install surface, like AGENTS.md. Standalone refresh:
276
+ # `rails g poetry:skill` (re-run after updating poetry gems).
277
+ # @api private
278
+ def write_skills
279
+ apply_poetry_skills
280
+ end
281
+
282
+ # Dark mode is one layout line, but nothing else in the install touches
283
+ # the layout - without the pointer the theme never applies before first
284
+ # paint and hosts rediscover the flash-of-light-mode pothole.
285
+ # @api private
286
+ def announce_color_scheme
287
+ say_status :note, "dark mode: render <%= poetry_color_scheme_script %> in your layout <head> " \
288
+ "(the Theming guide, \"Color scheme\")", :cyan
289
+ end
290
+
291
+ # poetry_optimistic_form reconciles failures via a Turbo refresh, which
292
+ # must MORPH to be seamless - two layout metas the install cannot add
293
+ # for the host (the redirect trap and server contract live in
294
+ # the doc).
295
+ # @api private
296
+ def announce_optimistic_form
297
+ say_status :note, "optimistic forms: add <meta name=\"turbo-refresh-method\" content=\"morph\"> " \
298
+ "+ <meta name=\"turbo-refresh-scroll\" content=\"preserve\"> to your layout " \
299
+ "<head> before using poetry_optimistic_form (the Optimistic Forms guide)", :cyan
300
+ end
301
+
302
+ private
303
+
304
+ def resolved_theme
305
+ @resolved_theme ||= options[:theme] || installed_theme || "default"
306
+ end
307
+
308
+ # Which theme fills the slot right now. Unreadable header (a
309
+ # hand-edited file, or an install predating theme selection) falls back
310
+ # to "default" WITH a warning - never silently.
311
+ def installed_theme
312
+ path = File.join(destination_root, STYLE_SLOT)
313
+ return nil unless File.exist?(path)
314
+
315
+ name = File.foreach(path).first.to_s[THEME_HEADER, 1]
316
+ if name.nil?
317
+ say_status :theme, "could not identify the installed theme from #{STYLE_SLOT} - " \
318
+ "installing \"default\" (pass --theme <name> to keep yours)", :yellow
319
+ elsif name != "default"
320
+ say_status :theme, "keeping installed theme #{name.inspect} (pass --theme to switch)", :cyan
321
+ end
322
+ name
323
+ end
324
+
325
+ def charts_available?
326
+ defined?(Poetry::Charts::Engine) ? true : false
327
+ end
328
+
329
+ def agent_available?
330
+ defined?(Poetry::Agent::Engine) ? true : false
331
+ end
332
+
333
+ # poetry-agent's WebMCP runtime registers beside the controllers when
334
+ # the gem is bundled (the engine already pins @poetry/agent); without
335
+ # it, `webmcp:` opt-ins have nothing to register with. Private: Thor
336
+ # would otherwise expose an argument-taking method as a command.
337
+ def register_agent_runtime(index)
338
+ return unless agent_available?
339
+
340
+ inject_unless_present(index, <<~JS.strip)
341
+ import { registerPoetryAgent } from "@poetry/agent"
342
+ registerPoetryAgent(application)
343
+ JS
344
+ end
345
+
346
+ def ui_theme_path
347
+ Poetry::Ui.root.join("themes/#{resolved_theme}.css")
348
+ end
349
+
350
+ def charts_theme_path
351
+ Poetry::Charts.root.join("themes/#{resolved_theme}.css")
352
+ end
353
+
354
+ # The idempotency lives in the file-mutation primitive, not the
355
+ # generator: appending is a no-op when
356
+ # the line is already present.
357
+ def inject_unless_present(relative, line)
358
+ path = File.join(destination_root, relative)
359
+ return if File.exist?(path) && File.read(path).include?(line)
360
+
361
+ append_to_file relative, "#{line}\n"
362
+ end
363
+ end
364
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Poetry
6
+ module Generators
7
+ # `rails g poetry:pagination [kaminari|pagy|will_paginate]` - copies a
8
+ # poetry adapter for the host's paginator(s) as OWNED host-app code
9
+ # (the copied-source doctrine; poetry:diff tracks drift). No argument:
10
+ # every paginator loaded in this app gets its adapter.
11
+ #
12
+ # The doctrine baked into every adapter: poetry owns the window
13
+ # (siblings/edges compute the visible pages) - the paginator's own
14
+ # window options deliberately do not apply, so pagination looks the
15
+ # same whichever gem drives it.
16
+ #
17
+ # @example
18
+ # bin/rails g poetry:pagination pagy
19
+ class PaginationGenerator < Rails::Generators::Base
20
+ source_root File.expand_path("templates", __dir__)
21
+
22
+ # Per-paginator adapter wiring: how to detect the gem, which
23
+ # template to copy, and where it lands.
24
+ ADAPTERS = {
25
+ "kaminari" => { detect: -> { defined?(::Kaminari) },
26
+ template: "kaminari_paginator.html.erb",
27
+ destination: "app/views/kaminari/_paginator.html.erb" },
28
+ "pagy" => { detect: -> { defined?(::Pagy) },
29
+ template: "poetry_pagy_helper.rb",
30
+ destination: "app/helpers/poetry_pagy_helper.rb" },
31
+ "will_paginate" => { detect: -> { defined?(::WillPaginate) },
32
+ template: "poetry_link_renderer.rb",
33
+ destination: "app/lib/poetry_link_renderer.rb" }
34
+ }.freeze
35
+
36
+ argument :paginator, type: :string, required: false,
37
+ banner: "kaminari|pagy|will_paginate",
38
+ desc: "the paginator to adapt (omit to detect every installed one)"
39
+
40
+ # Step: copies the adapter(s) for the requested or detected
41
+ # paginator(s).
42
+ # @api private
43
+ def install_adapters
44
+ names = requested_adapters
45
+ if names.empty?
46
+ say_status :skip, "no paginator detected (kaminari / pagy / will_paginate) - " \
47
+ "add one to the Gemfile first, or name it explicitly", :yellow
48
+ return
49
+ end
50
+
51
+ names.each do |name|
52
+ spec = ADAPTERS.fetch(name)
53
+ copy_file spec[:template], spec[:destination]
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def requested_adapters
60
+ if paginator.present?
61
+ unless ADAPTERS.key?(paginator)
62
+ raise Thor::Error, "unknown paginator #{paginator.inspect} - " \
63
+ "one of: #{ADAPTERS.keys.join(", ")}"
64
+ end
65
+ unless ADAPTERS.fetch(paginator)[:detect].call
66
+ raise Thor::Error, "#{paginator} is not loaded in this app - add the gem, bundle, and re-run"
67
+ end
68
+
69
+ [paginator]
70
+ else
71
+ ADAPTERS.select { |_name, spec| spec[:detect].call }.keys
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,29 @@
1
+ <%# generated by `bin/rails g poetry:pagination kaminari` - this file is
2
+ yours: edit freely. ONE monolithic template replaces kaminari's seven
3
+ theme partials. page_url_for (via a bare Tag) honors param_name (incl.
4
+ nested), the canonical param-less page 1, and kaminari's param
5
+ blocklist. (No paginator.render block here: its method_missing
6
+ delegation drops Ruby keyword arguments - call helpers directly.)
7
+
8
+ Doctrine: poetry owns the window - kaminari's window:/outer_window:
9
+ options do not apply. Pass poetry options straight through paginate:
10
+ paginate @records, siblings: 2, edges: :arrows
11
+ (unrecognized paginate options arrive here as locals). The
12
+ total_pages guard mirrors kaminari's own nil-at-one-page behavior. %>
13
+ <% if total_pages > 1 %>
14
+ <%# URL duty rides a bare Tag (the abstract base with public
15
+ page_url_for): released kaminari's Paginator skips Tag#initialize,
16
+ so page_url_for only works on the Paginator on unreleased master.
17
+ The Tag inherits param_name/params from the paginate call's options
18
+ (they arrive here as locals). %>
19
+ <% url_tag = Kaminari::Helpers::Tag.new(
20
+ self, **{ params: local_assigns[:params],
21
+ param_name: local_assigns[:param_name] }.compact) %>
22
+ <%= poetry_pagination(
23
+ current: current_page.to_i,
24
+ total: total_pages,
25
+ path: ->(page) { url_tag.page_url_for(page) },
26
+ **local_assigns.slice(:siblings, :edges, :pages, :current_variant,
27
+ :label, :previous_label, :next_label).compact
28
+ ) %>
29
+ <% end %>
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # generated by `bin/rails g poetry:pagination will_paginate` - this file is
4
+ # yours: edit freely. Usage (the renderer: option per call - will_paginate
5
+ # deprecates the global default):
6
+ #
7
+ # <%= will_paginate @records, renderer: PoetryLinkRenderer %>
8
+ #
9
+ # to_html is replaced wholesale (the upstream comment invites it); the
10
+ # inherited url(page) keeps will_paginate's GET-param merging and nested
11
+ # param_name support. Doctrine: poetry owns the window - inner_window/
12
+ # outer_window do not apply; will_paginate's container options are
13
+ # absorbed by poetry's own <nav> landmark. Upstream behavior kept: the
14
+ # helper returns nil when total_pages <= 1 (this class is never called).
15
+ #
16
+ # Note: will_paginate itself is in maintenance mode (no new features or
17
+ # releases since mid-2024). This adapter supports apps that already use
18
+ # it; for a new project, prefer kaminari or pagy.
19
+
20
+ # will_paginate wires its ActionView integration on_load(:action_view);
21
+ # eager-loading contexts (generators, some rake tasks) can autoload this
22
+ # class before that hook fires, so pull the superclass in explicitly.
23
+ require "will_paginate/view_helpers/action_view"
24
+
25
+ class PoetryLinkRenderer < WillPaginate::ActionView::LinkRenderer
26
+ def to_html
27
+ @template.poetry_pagination(
28
+ current: @collection.current_page.to_i,
29
+ total: @collection.total_pages,
30
+ path: ->(page) { url(page) },
31
+ **(@options[:poetry] || {})
32
+ )
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # generated by `bin/rails g poetry:pagination pagy` - this file is yours:
4
+ # edit freely. Usage, anywhere a view holds a @pagy:
5
+ #
6
+ # <%= poetry_pagy_nav(@pagy) %>
7
+ #
8
+ # Doctrine: poetry owns the window (siblings:/edges: options here) -
9
+ # pagy's slots: does not apply. Mirrors the other paginators' behavior of
10
+ # rendering nothing at a single page. Needs pagy >= 43 (the leap-version
11
+ # redesign: instance-method navs, page_url); pagy's *_js navs are
12
+ # deliberately not adapted - their token protocol couples to pagy.js.
13
+ module PoetryPagyHelper
14
+ def poetry_pagy_nav(pagy, **options)
15
+ unless Pagy::VERSION.to_i >= 43
16
+ raise "poetry_pagy_nav needs pagy >= 43 (found #{Pagy::VERSION}) - " \
17
+ "the pre-43 extras API is a different world"
18
+ end
19
+ return "".html_safe if pagy.last <= 1
20
+
21
+ poetry_pagination(
22
+ current: pagy.page.to_i,
23
+ total: pagy.last,
24
+ path: ->(page) { pagy.page_url(page) },
25
+ **options
26
+ )
27
+ end
28
+ end