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,131 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ // installed by the poetry `agent-embed` recipe - this file is yours: edit
4
+ // freely. An OPT-IN in-page GUI agent (page-agent, MIT, loaded from the
5
+ // pinned CDN build with ?autoInit=false - no auto agent, no demo LLM)
6
+ // configured for a poetry app. Wire a small form with this controller
7
+ // (targets: model, baseUrl, apiKey, status; actions: agent-demo#activate,
8
+ // agent-demo#deactivate) - see the reference demo at the poetry docs
9
+ // /agent page, whose loader this file mirrors.
10
+ //
11
+ // The three Turbo rules this encodes (learned the hard way):
12
+ // 1. Turbo replaces <body> on navigation and the agent panel lives there -
13
+ // remount on turbo:load.
14
+ // 2. Remount ONLY when idle: a running task survives the body swap
15
+ // headless and completes (URL-state components navigate mid-task);
16
+ // disposing mid-task aborts it out from under the agent.
17
+ // 3. On a hard load, the first turbo:load can fire before this module
18
+ // evaluates - catch up once at module init.
19
+ const SCRIPT_URL =
20
+ "https://cdn.jsdelivr.net/npm/page-agent@1.12.2/dist/iife/page-agent.demo.js?autoInit=false"
21
+ const FLAG = "poetry-agent-embed"
22
+
23
+ // Host-agnostic operator instructions for any poetry app. If your app
24
+ // serves its own operator register (a JSON of per-page instructions),
25
+ // point REGISTER_URL at it; absent, the agent still gets the poetry
26
+ // ground rules below plus the part contract via includeAttributes.
27
+ const REGISTER_URL = "/operator-register.json"
28
+ const POETRY_SYSTEM = `This site is built from poetry components. Every component stamps
29
+ data-component (its family) and data-slot (its parts) - trust those over guessing
30
+ from classes. Interactive components follow WAI-ARIA patterns; open surfaces are
31
+ portaled to the end of <body>, one at a time - never click through an overlay
32
+ scrim, dismiss first (Escape or outside click). Prefer clicking labels and options
33
+ over coordinates. Keyboard-first components (sliders, date/time segments, OTP
34
+ inputs): TYPE values into segments - typed input works where clicking alone never
35
+ sets them; sliders need arrow keys and track clicks are only coarse.`
36
+
37
+ let registerPromise = null
38
+
39
+ async function operatorRegister() {
40
+ registerPromise ||= fetch(REGISTER_URL)
41
+ .then((response) => (response.ok ? response.json() : null))
42
+ .catch(() => null)
43
+ return registerPromise
44
+ }
45
+
46
+ function pageInstructions(register, url) {
47
+ if (!register) return null
48
+ const path = new URL(url, window.location.origin).pathname
49
+ const hit = Object.entries(register.pages || {})
50
+ .filter(([prefix]) => path === prefix || path.startsWith(`${prefix}/`))
51
+ .sort((a, b) => b[0].length - a[0].length)[0]
52
+ return `${hit ? hit[1] : ""}\n${register.default || ""}`.trim() || null
53
+ }
54
+
55
+ async function mountAgent() {
56
+ if (window.pageAgent && !document.getElementById("page-agent-runtime_agent-panel") &&
57
+ window.pageAgent.status !== "running") {
58
+ try { window.pageAgent.dispose?.() } catch { /* replaced below */ }
59
+ window.pageAgent = undefined
60
+ }
61
+ if (window.pageAgent) return
62
+ const config = JSON.parse(sessionStorage.getItem(FLAG) || "null")
63
+ if (!config) return
64
+
65
+ if (!window.PageAgent) {
66
+ await new Promise((resolve, reject) => {
67
+ const script = document.createElement("script")
68
+ script.src = SCRIPT_URL
69
+ script.crossOrigin = "anonymous"
70
+ script.onload = resolve
71
+ script.onerror = reject
72
+ document.head.appendChild(script)
73
+ })
74
+ }
75
+
76
+ const register = await operatorRegister()
77
+ window.pageAgent = new window.PageAgent({
78
+ model: config.model,
79
+ baseURL: config.baseURL,
80
+ apiKey: config.apiKey,
81
+ language: "en-US",
82
+ includeAttributes: ["data-component", "data-slot"],
83
+ instructions: {
84
+ system: register?.system || POETRY_SYSTEM,
85
+ getPageInstructions: (url) => pageInstructions(register, url),
86
+ },
87
+ })
88
+ window.pageAgent.panel.show()
89
+ }
90
+
91
+ document.addEventListener("turbo:load", mountAgent)
92
+ mountAgent()
93
+
94
+ export default class extends Controller {
95
+ static targets = ["model", "baseUrl", "apiKey", "status"]
96
+
97
+ connect() {
98
+ if (sessionStorage.getItem(FLAG)) this.note("Agent is active - the panel follows you across pages.")
99
+ }
100
+
101
+ async activate(event) {
102
+ event.preventDefault()
103
+ const config = {
104
+ model: this.modelTarget.value.trim(),
105
+ baseURL: this.baseUrlTarget.value.trim(),
106
+ apiKey: this.apiKeyTarget.value.trim(),
107
+ }
108
+ if (!config.apiKey) return this.note("An API key is required (it stays in this browser session).")
109
+
110
+ sessionStorage.setItem(FLAG, JSON.stringify(config))
111
+ try {
112
+ await mountAgent()
113
+ this.note("Agent mounted.")
114
+ } catch (error) {
115
+ sessionStorage.removeItem(FLAG)
116
+ this.note(`Could not mount the agent: ${error.message || error}`)
117
+ }
118
+ }
119
+
120
+ deactivate(event) {
121
+ event.preventDefault()
122
+ sessionStorage.removeItem(FLAG)
123
+ window.pageAgent?.dispose?.()
124
+ window.pageAgent = undefined
125
+ this.note("Agent deactivated and its key cleared.")
126
+ }
127
+
128
+ note(text) {
129
+ this.statusTarget.textContent = text
130
+ }
131
+ }
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # installed by the poetry `screen-data-index` recipe - this file is yours:
4
+ # edit freely. Wire the route yourself (one line, so your routes.rb is
5
+ # never touched by an installer):
6
+ #
7
+ # resources :orders, only: :index
8
+ #
9
+ # The view renders the data-index block (installed alongside at
10
+ # app/views/blocks/_data_index.html.erb) with its sample rows; replace the
11
+ # block's static table with your records as you adapt it - the markup is
12
+ # yours too.
13
+ class OrdersController < ApplicationController
14
+ def index; end
15
+ end
@@ -0,0 +1,7 @@
1
+ <%# installed by the poetry `screen-data-index` recipe - yours to adapt.
2
+ The screen is the data-index block in a padded page frame; the block
3
+ partial is the same source `bin/rails g poetry:block data-index`
4
+ copies in. %>
5
+ <div class="mx-auto max-w-5xl p-6">
6
+ <%= render "blocks/data_index" %>
7
+ </div>
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "application_system_test_case"
4
+
5
+ # installed by the poetry `screen-data-index` recipe. Server-render
6
+ # assertions only - no JS driver required; the test proves the screen
7
+ # route renders the block's furniture.
8
+ class OrdersIndexTest < ApplicationSystemTestCase
9
+ test "the orders index renders the data-index screen" do
10
+ visit orders_path
11
+
12
+ assert_text "Orders"
13
+ assert_selector "table"
14
+ assert_text "New order"
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # installed by the poetry `screen-settings` recipe - this file is yours:
4
+ # edit freely. Wire the route yourself (one line, so your routes.rb is
5
+ # never touched by an installer):
6
+ #
7
+ # resource :settings, only: :show
8
+ #
9
+ # The view composes the page-header, section-card, and destructive-panel
10
+ # blocks (installed alongside under app/views/blocks/); adapt their copy
11
+ # and wire the actions to your models as you go.
12
+ class SettingsController < ApplicationController
13
+ def show; end
14
+ end
@@ -0,0 +1,9 @@
1
+ <%# installed by the poetry `screen-settings` recipe - yours to adapt.
2
+ A settings page composed from three blocks: the page furniture, a
3
+ contained settings section, and the guarded danger zone. The section
4
+ blocks carry their own page framing; the stack just orders them. %>
5
+ <div class="mx-auto flex max-w-3xl flex-col gap-8 p-6">
6
+ <%= render "blocks/page_header" %>
7
+ <%= render "blocks/section_card" %>
8
+ <%= render "blocks/destructive_panel" %>
9
+ </div>
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "application_system_test_case"
4
+
5
+ # installed by the poetry `screen-settings` recipe. Server-render
6
+ # assertions only - no JS driver required; the test proves the composed
7
+ # screen renders all three blocks.
8
+ class SettingsShowTest < ApplicationSystemTestCase
9
+ test "the settings page composes header, section, and danger zone" do
10
+ visit settings_path
11
+
12
+ assert_text "Atlas"
13
+ assert_text "Usage-based billing"
14
+ assert_text "Revoke deploy token"
15
+ end
16
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Poetry
6
+ # `rails g poetry:scaffold_templates`:
7
+ # Rails has always let an app override its generator templates
8
+ # from lib/templates/; what was missing was a set that renders with
9
+ # poetry. This copies scaffold view templates - and a matching scaffold
10
+ # controller template - so the STANDARD `rails g scaffold` produces
11
+ # poetry-composed output: a DataTable index with sanitized URL state
12
+ # (sortable whitelist, filter, pagination), Field-composed forms with
13
+ # attribute-type -> component mapping (plus column-name -> input-type
14
+ # heuristics and `null: false` -> required), a MetadataList show, and a
15
+ # destructive-variant delete. The copies are the app's to edit; re-runs
16
+ # never overwrite (skip-if-exists, the poetry:add contract).
17
+ #
18
+ # @example
19
+ # bin/rails g poetry:scaffold_templates
20
+ class ScaffoldTemplatesGenerator < Rails::Generators::Base
21
+ source_root File.expand_path("templates", __dir__)
22
+
23
+ # The scaffold view templates the override set ships.
24
+ VIEW_TEMPLATES = %w[index show new edit _form partial].freeze
25
+
26
+ # The --skip-controller option description (multi-line).
27
+ SKIP_CONTROLLER_DESC = "Skip the scaffold_controller override (index keeps the stock " \
28
+ "all-records query; the generated index view expects @state/@pages " \
29
+ "from the poetry controller template)"
30
+
31
+ class_option :skip_controller, type: :boolean, default: false, desc: SKIP_CONTROLLER_DESC
32
+
33
+ # Step: copies the scaffold view template overrides (skip-if-exists).
34
+ # @api private
35
+ def copy_view_templates
36
+ VIEW_TEMPLATES.each do |name|
37
+ copy_file "#{name}.html.erb.tt", "lib/templates/erb/scaffold/#{name}.html.erb.tt", skip: true
38
+ end
39
+ end
40
+
41
+ # Step: copies the scaffold controller override unless skipped.
42
+ # @api private
43
+ def copy_controller_template
44
+ return if options[:skip_controller]
45
+
46
+ copy_file "controller.rb.tt", "lib/templates/rails/scaffold_controller/controller.rb.tt", skip: true
47
+ end
48
+
49
+ # Step: prints what the override set changes.
50
+ # @api private
51
+ def show_next_steps
52
+ say ""
53
+ say "poetry scaffold templates installed.", :green
54
+ say " bin/rails generate scaffold Post title:string body:text"
55
+ say "now produces poetry-composed views#{" and a URL-state index controller" unless options[:skip_controller]}."
56
+ say "The templates under lib/templates/ are yours to edit."
57
+ say ""
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,77 @@
1
+ <%%= form_with(model: <%= model_resource_name %>, class: "flex max-w-xl flex-col gap-4") do |form| %>
2
+ <%% if <%= singular_table_name %>.errors.any? %>
3
+ <%%= poetry_alert(variant: :destructive) do |alert| %>
4
+ <%% alert.with_title { "#{pluralize(<%= singular_table_name %>.errors.count, "error")} prohibited this <%= human_name.downcase %> from being saved" } %>
5
+ <ul class="list-inside list-disc">
6
+ <%% <%= singular_table_name %>.errors.each do |error| %>
7
+ <li><%%= error.full_message %></li>
8
+ <%% end %>
9
+ </ul>
10
+ <%% end %>
11
+ <%% end %>
12
+
13
+ <% attributes.each do |attribute| -%>
14
+ <% required = attribute.attr_options[:null] == false -%>
15
+ <% if attribute.password_digest? -%>
16
+ <%%= poetry_field(id: form.field_id(:password), label_text: "Password",
17
+ error: <%= singular_table_name %>.errors[:password].first) do |field| %>
18
+ <%%= poetry_input(type: "password", name: form.field_name(:password),
19
+ **field.control_attributes.transform_keys(&:to_sym)) %>
20
+ <%% end %>
21
+
22
+ <%%= poetry_field(id: form.field_id(:password_confirmation), label_text: "Password confirmation") do |field| %>
23
+ <%%= poetry_input(type: "password", name: form.field_name(:password_confirmation),
24
+ **field.control_attributes.transform_keys(&:to_sym)) %>
25
+ <%% end %>
26
+ <% elsif attribute.type.to_s == "boolean" -%>
27
+ <%%= poetry_checkbox(label: "<%= attribute.human_name %>", name: form.field_name(:<%= attribute.column_name %>),
28
+ checked: <%= model_resource_name %>.<%= attribute.column_name %>?, value: "1", unchecked_value: "0") %>
29
+ <% elsif %w[integer decimal float].include?(attribute.type.to_s) -%>
30
+ <%%= poetry_number_field(label: "<%= attribute.human_name %>", name: form.field_name(:<%= attribute.column_name %>),
31
+ value: <%= model_resource_name %>.<%= attribute.column_name %><%= attribute.type.to_s == "integer" ? ", step: 1" : "" %><%= ", required: true" if required %>) %>
32
+ <% elsif attribute.type.to_s == "date" -%>
33
+ <%%= poetry_date_field(label: "<%= attribute.human_name %>", name: form.field_name(:<%= attribute.column_name %>),
34
+ value: <%= model_resource_name %>.<%= attribute.column_name %><%= ", required: true" if required %>) %>
35
+ <% elsif %w[text rich_text].include?(attribute.type.to_s) -%>
36
+ <%%= poetry_field(id: form.field_id(:<%= attribute.column_name %>), label_text: "<%= attribute.human_name %>",<%= " required: true," if required %>
37
+ error: <%= singular_table_name %>.errors[:<%= attribute.column_name %>].first) do |field| %>
38
+ <%%= poetry_textarea(name: form.field_name(:<%= attribute.column_name %>),
39
+ value: <%= model_resource_name %>.<%= attribute.column_name %>,
40
+ **field.control_attributes.transform_keys(&:to_sym)) %>
41
+ <%% end %>
42
+ <% elsif attribute.attachment? || attribute.attachments? -%>
43
+ <%%= poetry_field(id: form.field_id(:<%= attribute.column_name %>), label_text: "<%= attribute.human_name %>") do |field| %>
44
+ <%%= poetry_input(type: "file", name: form.field_name(:<%= attribute.column_name %><%= attribute.attachments? ? ", multiple: true" : "" %>),
45
+ <%= attribute.attachments? ? "multiple: true, " : "" %>**field.control_attributes.transform_keys(&:to_sym)) %>
46
+ <%% end %>
47
+ <% else -%>
48
+ <% value_expr = case attribute.type.to_s
49
+ when "datetime" then "#{model_resource_name}.#{attribute.column_name}&.strftime(\"%Y-%m-%dT%H:%M\")"
50
+ when "time" then "#{model_resource_name}.#{attribute.column_name}&.strftime(\"%H:%M\")"
51
+ else "#{model_resource_name}.#{attribute.column_name}"
52
+ end
53
+ input_type = if attribute.type.to_s != "string"
54
+ { "datetime" => "datetime-local", "time" => "time" }.fetch(attribute.type.to_s, "text")
55
+ elsif %w[email email_address].include?(attribute.column_name)
56
+ "email"
57
+ elsif %w[phone phone_number].include?(attribute.column_name)
58
+ "tel"
59
+ elsif %w[url website permalink].include?(attribute.column_name)
60
+ "url"
61
+ else
62
+ "text"
63
+ end -%>
64
+ <%%= poetry_field(id: form.field_id(:<%= attribute.column_name %>), label_text: "<%= attribute.human_name %>",<%= " required: true," if required %>
65
+ error: <%= singular_table_name %>.errors[:<%= attribute.column_name %>].first) do |field| %>
66
+ <%%= poetry_input(type: "<%= input_type %>", name: form.field_name(:<%= attribute.column_name %>),
67
+ value: <%= value_expr %>,
68
+ **field.control_attributes.transform_keys(&:to_sym)) %>
69
+ <%% end %>
70
+ <% end -%>
71
+ <% end -%>
72
+
73
+ <div class="flex items-center gap-3">
74
+ <%%= poetry_button(type: :submit) { <%= model_resource_name %>.persisted? ? "Update <%= human_name.downcase %>" : "Create <%= human_name.downcase %>" } %>
75
+ <%%= poetry_button(tag: :a, variant: :ghost, href: <%= index_helper(type: :path) %>) { "Cancel" } %>
76
+ </div>
77
+ <%% end %>
@@ -0,0 +1,78 @@
1
+ <% module_namespacing do -%>
2
+ class <%= controller_class_name %>Controller < ApplicationController
3
+ PER_PAGE = 25
4
+
5
+ before_action :set_<%= singular_table_name %>, only: %i[ show edit update destroy ]
6
+
7
+ # GET <%= route_url %> - sort/filter/page arrive as sanitized URL state
8
+ # (the poetry DataTable contract): State whitelists the sort column, so
9
+ # order_clause is injection-safe by construction.
10
+ def index
11
+ @state = Poetry::Ui::DataTable::State.from_params(
12
+ params,
13
+ sortable: %w[<%= attributes.reject { |a| a.attachment? || a.attachments? || a.rich_text? || a.password_digest? }.map(&:column_name).join(" ") %>]
14
+ )
15
+ scope = <%= class_name %>.order(@state.order_clause)
16
+ <% filter_column = attributes.find { |a| !a.password_digest? && a.type.to_s == "string" } -%>
17
+ <% if filter_column -%>
18
+ scope = scope.where("<%= filter_column.column_name %> LIKE ?", "%#{@state.q}%") if @state.q.present?
19
+ <% end -%>
20
+ @pages = [(scope.count.to_f / PER_PAGE).ceil, 1].max
21
+ @<%= plural_table_name %> = scope.offset((@state.page - 1) * PER_PAGE).limit(PER_PAGE)
22
+ end
23
+
24
+ # GET <%= route_url %>/1
25
+ def show
26
+ end
27
+
28
+ # GET <%= route_url %>/new
29
+ def new
30
+ @<%= singular_table_name %> = <%= orm_class.build(class_name) %>
31
+ end
32
+
33
+ # GET <%= route_url %>/1/edit
34
+ def edit
35
+ end
36
+
37
+ # POST <%= route_url %>
38
+ def create
39
+ @<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %>
40
+
41
+ if @<%= orm_instance.save %>
42
+ redirect_to <%= redirect_resource_name %>, notice: <%= %("#{human_name} was successfully created.") %>
43
+ else
44
+ render :new, status: <%= ActionDispatch::Constants::UNPROCESSABLE_CONTENT.inspect %>
45
+ end
46
+ end
47
+
48
+ # PATCH/PUT <%= route_url %>/1
49
+ def update
50
+ if @<%= orm_instance.update("#{singular_table_name}_params") %>
51
+ redirect_to <%= redirect_resource_name %>, notice: <%= %("#{human_name} was successfully updated.") %>, status: :see_other
52
+ else
53
+ render :edit, status: <%= ActionDispatch::Constants::UNPROCESSABLE_CONTENT.inspect %>
54
+ end
55
+ end
56
+
57
+ # DELETE <%= route_url %>/1
58
+ def destroy
59
+ @<%= orm_instance.destroy %>
60
+ redirect_to <%= index_helper %>_path, notice: <%= %("#{human_name} was successfully destroyed.") %>, status: :see_other
61
+ end
62
+
63
+ private
64
+ # Use callbacks to share common setup or constraints between actions.
65
+ def set_<%= singular_table_name %>
66
+ @<%= singular_table_name %> = <%= orm_class.find(class_name, "params.expect(:id)") %>
67
+ end
68
+
69
+ # Only allow a list of trusted parameters through.
70
+ def <%= "#{singular_table_name}_params" %>
71
+ <%- if attributes_names.empty? -%>
72
+ params.fetch(:<%= singular_table_name %>, {})
73
+ <%- else -%>
74
+ params.expect(<%= singular_table_name %>: [ <%= permitted_params %> ])
75
+ <%- end -%>
76
+ end
77
+ end
78
+ <% end -%>
@@ -0,0 +1,14 @@
1
+ <%% content_for :title, "Editing <%= human_name.downcase %>" %>
2
+
3
+ <div class="flex max-w-xl flex-col gap-6">
4
+ <header class="flex flex-col gap-1">
5
+ <h1 class="text-2xl font-semibold tracking-tight">Editing <%= human_name.downcase %></h1>
6
+ </header>
7
+
8
+ <%%= render "form", <%= singular_table_name %>: @<%= singular_table_name %> %>
9
+
10
+ <div class="flex items-center gap-2">
11
+ <%%= poetry_button(tag: :a, variant: :outline, href: <%= show_helper(type: :path) %>) { "Show this <%= human_name.downcase %>" } %>
12
+ <%%= poetry_button(tag: :a, variant: :ghost, href: <%= index_helper(type: :path) %>) { "Back to <%= human_name.pluralize.downcase %>" } %>
13
+ </div>
14
+ </div>
@@ -0,0 +1,24 @@
1
+ <% data_columns = attributes.reject { |a| a.attachment? || a.attachments? || a.rich_text? || a.password_digest? } -%>
2
+ <% has_filter = data_columns.any? { |a| a.type.to_s == "string" } -%>
3
+ <%% content_for :title, "<%= human_name.pluralize %>" %>
4
+
5
+ <div class="flex flex-col gap-6">
6
+ <header class="flex flex-wrap items-end justify-between gap-3">
7
+ <h1 class="text-2xl font-semibold tracking-tight"><%= human_name.pluralize %></h1>
8
+ <%%= poetry_button(tag: :a, href: <%= new_helper(type: :path) %>) { "New <%= human_name.downcase %>" } %>
9
+ </header>
10
+
11
+ <%%= poetry_data_table(rows: @<%= plural_table_name %>, state: @state, total: @pages,
12
+ path: ->(p) { <%= index_helper(type: :path) %>(p) },
13
+ caption: "<%= human_name.pluralize %>",
14
+ filter: <%= has_filter %>,
15
+ empty_text: "No <%= human_name.pluralize.downcase %> yet.") do |t| %>
16
+ <% data_columns.each do |attribute| -%>
17
+ <%% t.with_column("<%= attribute.human_name %>", key: :<%= attribute.column_name %>, sortable: true) { |<%= singular_table_name %>| <%= singular_table_name %>.<%= attribute.column_name %> } %>
18
+ <% end -%>
19
+ <% attributes.select { |a| a.attachment? || a.attachments? || a.rich_text? }.each do |attribute| -%>
20
+ <%% t.with_column("<%= attribute.human_name %>") { |<%= singular_table_name %>| <%= singular_table_name %>.<%= attribute.column_name %> } %>
21
+ <% end -%>
22
+ <%% t.with_column("", classes: "text-right") { |<%= singular_table_name %>| poetry_button(tag: :a, variant: :ghost, size: :sm, href: <%= singular_table_name %>_path(<%= singular_table_name %>)) { "Show" } } %>
23
+ <%% end %>
24
+ </div>
@@ -0,0 +1,11 @@
1
+ <%% content_for :title, "New <%= human_name.downcase %>" %>
2
+
3
+ <div class="flex max-w-xl flex-col gap-6">
4
+ <header class="flex flex-col gap-1">
5
+ <h1 class="text-2xl font-semibold tracking-tight">New <%= human_name.downcase %></h1>
6
+ </header>
7
+
8
+ <%%= render "form", <%= singular_table_name %>: @<%= singular_table_name %> %>
9
+
10
+ <%%= poetry_button(tag: :a, variant: :ghost, href: <%= index_helper(type: :path) %>) { "Back to <%= human_name.pluralize.downcase %>" } %>
11
+ </div>
@@ -0,0 +1,13 @@
1
+ <div id="<%%= dom_id <%= singular_name %> %>">
2
+ <%%= poetry_metadata_list do |list| %>
3
+ <% attributes.reject(&:password_digest?).each do |attribute| -%>
4
+ <% if attribute.attachment? -%>
5
+ <%% list.with_item(label: "<%= attribute.human_name %>") { (link_to <%= singular_name %>.<%= attribute.column_name %>.filename, <%= singular_name %>.<%= attribute.column_name %>) if <%= singular_name %>.<%= attribute.column_name %>.attached? } %>
6
+ <% elsif attribute.attachments? -%>
7
+ <%% list.with_item(label: "<%= attribute.human_name %>") { safe_join(<%= singular_name %>.<%= attribute.column_name %>.map { |file| link_to(file.filename, file) }, ", ") } %>
8
+ <% else -%>
9
+ <%% list.with_item(label: "<%= attribute.human_name %>") { <%= singular_name %>.<%= attribute.column_name %> } %>
10
+ <% end -%>
11
+ <% end -%>
12
+ <%% end %>
13
+ </div>
@@ -0,0 +1,18 @@
1
+ <%% content_for :title, "<%= human_name %>" %>
2
+
3
+ <div class="flex max-w-2xl flex-col gap-6">
4
+ <header class="flex flex-wrap items-end justify-between gap-3">
5
+ <h1 class="text-2xl font-semibold tracking-tight"><%= human_name %></h1>
6
+ <div class="flex items-center gap-2">
7
+ <%%= poetry_button(tag: :a, variant: :outline, href: <%= edit_helper(type: :path) %>) { "Edit" } %>
8
+ <%%= poetry_button(tag: :a, variant: :ghost, href: <%= index_helper(type: :path) %>) { "Back to <%= human_name.pluralize.downcase %>" } %>
9
+ </div>
10
+ </header>
11
+
12
+ <%%= render @<%= singular_table_name %> %>
13
+
14
+ <%%= form_with(model: @<%= singular_table_name %>, method: :delete,
15
+ data: { turbo_confirm: "Are you sure?" }) do %>
16
+ <%%= poetry_button(type: :submit, variant: :destructive) { "Destroy this <%= human_name.downcase %>" } %>
17
+ <%% end %>
18
+ </div>
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require_relative "../skills_section"
5
+
6
+ module Poetry
7
+ # `rails g poetry:skill` - installs or refreshes poetry's Claude Code
8
+ # skills in the app's .claude/skills/:
9
+ #
10
+ # poetry/ the component-usage skill, GENERATED from the live
11
+ # registry (SKILL.md menu + per-family references) -
12
+ # re-run this generator after updating poetry gems
13
+ # poetry-design/ the taste layer (theme / compose / audit / study),
14
+ # curated prose riding the DESIGN.md + design-lint rails
15
+ # poetry-component/ the authoring layer (anatomy / documentation /
16
+ # audit) for components the app builds itself
17
+ #
18
+ # poetry:install runs the same step; this standalone exists as the
19
+ # refresh path (and for hosts that installed before the skills shipped).
20
+ #
21
+ # @example
22
+ # bin/rails g poetry:skill
23
+ class SkillGenerator < Rails::Generators::Base
24
+ include Generators::SkillsSection
25
+
26
+ desc "Install the poetry Claude Code skills (usage + design + authoring) into .claude/skills/"
27
+
28
+ # Step: applies the three skill directories.
29
+ # @api private
30
+ def install_skills
31
+ apply_poetry_skills
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: poetry-component
3
+ description: >-
4
+ Author and audit poetry components: the canonical class anatomy
5
+ (section order every component follows), the documentation standard
6
+ (YARD + projected strings), and the audit checklist. Load this
7
+ WHENEVER creating a new component that inherits from
8
+ Poetry::Core::Component, extending an existing component family, or
9
+ reviewing a component file for structure and documentation - the
10
+ anatomy is the contract that keeps every component readable the same
11
+ way.
12
+ ---
13
+
14
+ # poetry-component - the authoring layer
15
+
16
+ poetry's catalog guarantees valid, accessible components; this skill
17
+ covers building one of your own. A component that follows the canonical
18
+ anatomy is legible to every reader - human, agent, and the machine
19
+ surfaces (registry, contract checks, docs) that project from its
20
+ declarations. Load this before writing the class, not after it fails
21
+ review.
22
+
23
+ ## Verbs
24
+
25
+ - **create** (`references/anatomy.md`) - author a new component: the
26
+ section order, what each DSL declares, and a complete skeleton to
27
+ start from.
28
+ - **document** (`references/documentation.md`) - the documentation
29
+ standard: the class docblock, YARD on public methods, and how to write
30
+ the projected strings (part descriptions, agent rules) that machines
31
+ consume verbatim.
32
+ - **audit** (`references/checklist.md`) - review an existing component
33
+ against the anatomy and the documentation standard; the checklist is
34
+ ordered so violations surface in file order.
35
+
36
+ ## The one rule that spans all three
37
+
38
+ Declaration order inside a section is meaningful: options, parts, and
39
+ slots project into the registry and the agent surface in declaration
40
+ order. Move whole sections to match the anatomy; never reorder
41
+ declarations within a section unless you intend to change the projected
42
+ surface.
43
+
44
+ ## Boundaries
45
+
46
+ - A new component inherits from `Poetry::Core::Component` (or a
47
+ published family's base) and lives in its own `app/components`
48
+ namespace. Never copy a poetry component's source into the app to
49
+ modify it - subclass it, or compose it in a template.
50
+ - Inner classes that exist only to serve a family (item builders,
51
+ internal wrappers) declare `internal_component!` so the registry and
52
+ every surface derived from it skip them.
53
+ - Behavior belongs to Stimulus controllers declared through
54
+ `use_stimulus`; a component never ships inline `<script>`.
55
+ - Agent tools: declare what an in-page agent may do to a rendered instance
56
+ with `tool :name, description:, params:, executes:, mutating:` beside
57
+ `use_stimulus` (`executes:` names a declared Stimulus action - validated at
58
+ class load); a call opts in with `webmcp: "name"`. See `references/anatomy.md`.