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,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Dated event sequences as ordered lists.
6
+ module Timeline
7
+ # A sequence of dated events rendered as a real ordered list
8
+ # (<ol>/<li>) - activity feeds, order status, deploy history. Each
9
+ # item carries a decorative indicator (a dot, or icon:'s glyph) on
10
+ # a connector rail; completed: marks progress and recolors the
11
+ # item's indicator and rail segment. orientation: :horizontal lays
12
+ # the steps left-to-right (an order tracker); the vertical default
13
+ # reads as a feed.
14
+ #
15
+ # A Timeline records history - for steps the user advances through,
16
+ # use Stepper. Styling is utility-only: the data-slot names are the
17
+ # restyle seam.
18
+ #
19
+ # @example
20
+ # render Poetry::Ui::Timeline::Component.new do |timeline|
21
+ # timeline.with_item(title: "Order placed", time: "Mar 15", completed: true)
22
+ # timeline.with_item(title: "In transit") { "Estimated delivery Thursday." }
23
+ # end
24
+ class Component < Poetry::Core::Component
25
+ # Projected into the registry, llms.txt, and the agent surface.
26
+ AGENT_RULES = [
27
+ "A sequence of dated events (activity feed, order status, deploy history) is a " \
28
+ "Timeline - never a hand-rolled stack of dots and left borders (this is the <ol> " \
29
+ "the sequence owes its readers).",
30
+ "Each with_item takes title:, optional time: (renders a <time>), optional icon: " \
31
+ "(swaps the dot for a glyph), completed: for progress - the description is the block.",
32
+ "completed: colors the item's indicator and its rail segment - mark every step up to " \
33
+ "the current one, not just the latest.",
34
+ "orientation: :horizontal lays the steps left-to-right (an order tracker); the " \
35
+ "vertical default reads as a feed. For steps the USER advances through, use " \
36
+ "Stepper - a Timeline records, it never navigates."
37
+ ].freeze
38
+
39
+ # The same fact the before_render raise enforces, stated
40
+ # statically: poetry check flags the omission without rendering.
41
+ REQUIRED_SLOTS = { item: "at least one item (title:, with the description as its block)" }.freeze
42
+
43
+ renders_many :items,
44
+ doc: "Declares one event: title:, optional time: (renders a <time>), optional icon: (replaces " \
45
+ "the dot), completed: for progress - the description is the block.",
46
+ renders: lambda { |title:, time: nil, icon: nil, completed: false,
47
+ **options, &block|
48
+ # class: merges through the dictionary (caller classes win on
49
+ # conflicts) - a plain hash merge would REPLACE css(:item).
50
+ attrs = { "data-slot" => "timeline-item", class: css(:item, class: options.delete(:class)) }
51
+ attrs["data-completed"] = "" if completed
52
+ content_tag(:li, attrs.merge(options)) do
53
+ body = [item_indicator(icon), item_separator, item_header(title, time)]
54
+ if block
55
+ body << content_tag(:div, { "data-slot" => "timeline-content",
56
+ class: css(:content) }, &block)
57
+ end
58
+ safe_join(body)
59
+ end
60
+ }
61
+
62
+ style :orientation, default: :vertical, variants: %i[vertical horizontal],
63
+ doc: "The layout axis: :vertical reads as a feed, :horizontal as a step tracker."
64
+
65
+ part "timeline", "The <ol> root - the event sequence",
66
+ states: {
67
+ "data-orientation" => { condition: "always - the layout axis",
68
+ values: %w[vertical horizontal] }
69
+ }
70
+ part "timeline-item", "One event (<li>): indicator + rail segment + header + description",
71
+ states: {
72
+ "data-completed" => "the step is done - recolors its indicator and rail segment"
73
+ }
74
+ part "timeline-indicator", "The decorative marker on the rail - a dot, or icon:'s glyph " \
75
+ "(aria-hidden; the sequence lives in the list semantics)"
76
+ part "timeline-separator", "The decorative rail segment toward the next item - hidden " \
77
+ "on the last"
78
+ part "timeline-header", "The title/time row"
79
+ part "timeline-title", "The event's name"
80
+ part "timeline-time", "The event's <time> - muted, small"
81
+ part "timeline-content", "Muted description under the header (the item's block)"
82
+
83
+ # @api private
84
+ def before_render
85
+ raise ArgumentError, "Timeline requires at least one with_item" unless items?
86
+ end
87
+
88
+ # @api private
89
+ def call
90
+ content_tag(:ol, safe_join(items.map(&:to_s)), **root_attributes.to_attributes)
91
+ end
92
+
93
+ # @api private
94
+ def root_attributes
95
+ html_attributes.merge_if_not_set(
96
+ { "data-slot" => "timeline", "data-orientation" => orientation }
97
+ .merge(component_data_attributes)
98
+ )
99
+ end
100
+
101
+ private
102
+
103
+ # Decorative twins (the Tree toggle-spacer rule): indicator and
104
+ # rail carry no semantics - the sequence is the <ol>, progress is
105
+ # data-completed.
106
+ def item_indicator(icon)
107
+ content_tag(:div, "data-slot" => "timeline-indicator", "aria-hidden" => "true",
108
+ "class" => css(:indicator)) do
109
+ if icon
110
+ render(Icon::Component.new(name: icon, class: css(:indicator_icon)))
111
+ else
112
+ content_tag(:span, nil, class: css(:indicator_dot))
113
+ end
114
+ end
115
+ end
116
+
117
+ def item_separator
118
+ content_tag(:div, nil, "data-slot" => "timeline-separator", "aria-hidden" => "true",
119
+ "class" => css(:separator))
120
+ end
121
+
122
+ def item_header(title, time)
123
+ content_tag(:div, "data-slot" => "timeline-header", class: css(:header)) do
124
+ parts = [content_tag(:div, title, "data-slot" => "timeline-title", class: css(:title))]
125
+ parts << content_tag(:time, time, "data-slot" => "timeline-time", class: css(:time)) if time
126
+ safe_join(parts)
127
+ end
128
+ end
129
+
130
+ private :root_attributes
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Timeline
6
+ class Preview < Poetry::Core::Preview::Base
7
+ def default
8
+ render_component do |timeline|
9
+ timeline.with_item(title: "Order placed", time: "Mar 15, 09:12", completed: true) do
10
+ "Payment authorized and inventory reserved."
11
+ end
12
+ timeline.with_item(title: "Packed", time: "Mar 15, 14:03", completed: true) do
13
+ "Two parcels sealed at dock B."
14
+ end
15
+ timeline.with_item(title: "In transit", time: "Mar 16") do
16
+ "Handed to the carrier - estimated delivery Thursday."
17
+ end
18
+ timeline.with_item(title: "Delivered")
19
+ end
20
+ end
21
+
22
+ # The order tracker: steps left-to-right, progress recoloring the
23
+ # rail up to the current step.
24
+ def horizontal
25
+ render_component(orientation: :horizontal) do |timeline|
26
+ timeline.with_item(title: "Ordered", time: "Mar 15", completed: true)
27
+ timeline.with_item(title: "Packed", time: "Mar 15", completed: true)
28
+ timeline.with_item(title: "Shipped", time: "Mar 16")
29
+ timeline.with_item(title: "Delivered")
30
+ end
31
+ end
32
+
33
+ # icon: swaps the dot for a glyph - the indicator stays decorative.
34
+ def with_icons
35
+ render_component do |timeline|
36
+ timeline.with_item(title: "Repository created", time: "Jun 2",
37
+ icon: :"git-branch", completed: true) do
38
+ "poetry-ui scaffolded from the gem template."
39
+ end
40
+ timeline.with_item(title: "First deploy", time: "Jun 4",
41
+ icon: :rocket, completed: true) do
42
+ "Shipped to staging behind the feature flag."
43
+ end
44
+ timeline.with_item(title: "Incident opened", time: "Jun 9", icon: :"triangle-alert") do
45
+ "Elevated 5xx rate on checkout - rolled back in 12 minutes."
46
+ end
47
+ end
48
+ end
49
+
50
+ # Titles alone: the anatomy holds without times or descriptions.
51
+ def titles_only
52
+ render_component do |timeline|
53
+ timeline.with_item(title: "Draft", completed: true)
54
+ timeline.with_item(title: "In review")
55
+ timeline.with_item(title: "Published")
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Timeline
6
+ # Utility-only (the Separator/Spinner rule): no upstream cn hook
7
+ # exists for a Timeline, so the surface is structural utilities on
8
+ # semantic tokens - data-slot selectors are the restyle seam. Both
9
+ # orientations live in ONE dictionary via the named-group pattern:
10
+ # the root carries group/timeline + data-orientation, each item
11
+ # group/timeline-item + data-completed, and every element restyles
12
+ # itself off those two markers - so one attribute flip re-lays the
13
+ # whole sequence.
14
+ class Style < Poetry::Core::Style
15
+ base "group/timeline flex"
16
+
17
+ variant :orientation, {
18
+ vertical: "flex-col",
19
+ horizontal: "w-full flex-row"
20
+ }
21
+
22
+ element :item, "group/timeline-item relative flex flex-1 flex-col gap-0.5 " \
23
+ "group-data-[orientation=vertical]/timeline:ps-9 " \
24
+ "group-data-[orientation=vertical]/timeline:not-last:pb-7 " \
25
+ "group-data-[orientation=horizontal]/timeline:pt-9 " \
26
+ "group-data-[orientation=horizontal]/timeline:not-last:pe-8"
27
+ element :indicator, "absolute top-0 left-0 flex size-6 items-center justify-center " \
28
+ "rounded-full border border-border bg-background " \
29
+ "text-muted-foreground " \
30
+ "group-data-[completed]/timeline-item:border-primary " \
31
+ "group-data-[completed]/timeline-item:text-primary"
32
+ element :indicator_dot, "size-1.5 rounded-full bg-current"
33
+ element :indicator_icon, "size-3.5"
34
+ element :separator, "absolute bg-border group-last/timeline-item:hidden " \
35
+ "group-data-[completed]/timeline-item:bg-primary " \
36
+ "group-data-[orientation=vertical]/timeline:top-7 " \
37
+ "group-data-[orientation=vertical]/timeline:left-3 " \
38
+ "group-data-[orientation=vertical]/timeline:h-[calc(100%-2rem)] " \
39
+ "group-data-[orientation=vertical]/timeline:w-px " \
40
+ "group-data-[orientation=vertical]/timeline:-translate-x-1/2 " \
41
+ "group-data-[orientation=horizontal]/timeline:top-3 " \
42
+ "group-data-[orientation=horizontal]/timeline:left-7 " \
43
+ "group-data-[orientation=horizontal]/timeline:h-px " \
44
+ "group-data-[orientation=horizontal]/timeline:w-[calc(100%-2rem)] " \
45
+ "group-data-[orientation=horizontal]/timeline:-translate-y-1/2"
46
+ element :header, "flex flex-wrap items-baseline gap-x-2 gap-y-0.5 " \
47
+ "group-data-[orientation=vertical]/timeline:min-h-6"
48
+ element :title, "text-sm font-medium"
49
+ element :time, "text-xs font-medium text-muted-foreground"
50
+ element :content, "text-sm text-muted-foreground"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,24 @@
1
+ <%= content_tag(:li, **root_attributes.to_attributes) do %>
2
+ <% if variant_icon %>
3
+ <span data-slot="toast-icon" aria-hidden="true" class="<%= css(:icon) %>">
4
+ <%= render Poetry::Ui::Icon::Component.new(name: variant_icon,
5
+ class: ("animate-spin" if variant == :loading)) %>
6
+ </span>
7
+ <% end %>
8
+ <div class="<%= css(:body) %>">
9
+ <div data-slot="toast-title" class="<%= css(:title) %>"><%= title %></div>
10
+ <% if description? %>
11
+ <div data-slot="toast-description" class="<%= css(:description) %>"><%= description %></div>
12
+ <% end %>
13
+ <%= content %>
14
+ </div>
15
+ <% if action? %><span class="<%= css(:action) %>"><%= action %></span><% end %>
16
+ <% if show_close_button %>
17
+ <%= render Poetry::Ui::Button::Component.new(variant: :ghost, size: :"icon-sm",
18
+ label: t("poetry.toast.close"),
19
+ class: css(:close),
20
+ **close_button_attributes) do %>
21
+ <%= render Poetry::Ui::Icon::Component.new(name: :x) %>
22
+ <% end %>
23
+ <% end %>
24
+ <% end %>
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Transient notifications.
6
+ module Toast
7
+ # The closed vocabulary for the variant axis.
8
+ VARIANTS = %i[default success info warning destructive loading].freeze
9
+ # The closed vocabulary for announcement politeness.
10
+ POLITENESS = %i[polite assertive].freeze
11
+
12
+ # One notification, rendered inside a Toaster region. The item
13
+ # itself is role=status with aria-live off: it never announces
14
+ # itself - on arrival the controller speaks it exactly once through
15
+ # a shared live region at the variant's politeness (:destructive
16
+ # announces assertively), so duplicating the announcement by hand
17
+ # is a regression.
18
+ #
19
+ # The auto-dismiss timer pauses on hover, focus-within, window
20
+ # blur, and hidden tabs; duration <= 0 means persistent - and an
21
+ # action-bearing toast defaults to persistent (a missable undo is a
22
+ # bug). Toasts never steal focus on show; the toaster's hotkey
23
+ # (F8 by default) reaches them.
24
+ #
25
+ # @example An undo toast (persistent because it carries an action)
26
+ # render Poetry::Ui::Toast::Component.new(variant: :success) do |toast|
27
+ # toast.with_title { "Message archived" }
28
+ # toast.with_action { "Undo" }
29
+ # end
30
+ class Component < Poetry::Core::Component
31
+ # The icon each variant renders (:default ships none; :loading spins).
32
+ VARIANT_ICONS = {
33
+ success: :"circle-check", info: :info, warning: :"triangle-alert",
34
+ destructive: :"octagon-x", loading: :"loader-circle"
35
+ }.freeze
36
+
37
+ # Projected into the registry, llms.txt, and the agent surface.
38
+ AGENT_RULES = [
39
+ "Server-side toasts go through turbo_stream.poetry_toast / the flash recipe - never " \
40
+ "hand-append into #poetry-toaster.",
41
+ "Undo/consequence toasts MUST carry an action slot - action-bearing toasts default to " \
42
+ "persistent (duration nil); give an explicit duration only when missing the action is safe.",
43
+ "variant: :destructive is for failures the user must hear about (it announces ASSERTIVELY); " \
44
+ "do not use it for styling.",
45
+ "Never put required interactions in a toast (toasts are missable) - that is AlertDialog.",
46
+ "Toasts are supplementary: never the only place an outcome is recorded."
47
+ ].freeze
48
+
49
+ # The same facts the before_render raise enforces, stated statically:
50
+ # poetry check flags the omission without rendering.
51
+ REQUIRED_SLOTS = { title: "the message" }.freeze
52
+
53
+ # The component behind the forwarding slot: with_action renders a Button.
54
+ SLOT_RENDERS = { action: Button::Component }.freeze
55
+
56
+ renders_one :title, doc: "The message (REQUIRED - the announced payload's first line)."
57
+
58
+ renders_one :description, doc: "Supporting copy under the title."
59
+
60
+ renders_one :action,
61
+ doc: "Typed Button slot (undo / view / retry): clicking it dismisses the toast with reason " \
62
+ "\"action\". Its presence makes the toast persistent by default.",
63
+ renders: lambda { |**options, &block|
64
+ wiring = { "data-slot" => "toast-action" }.merge(stimulus_attributes_for(:action))
65
+ # Caller attribute keys merge WITH the wiring (stimulus concat)
66
+ # instead of replacing it at the kwargs splat; component options
67
+ # (variant: etc.) stay plain kwargs.
68
+ attr_keys = options.keys.select { |k| k == :data || k.to_s.start_with?("data-", "aria-") }
69
+ caller_attrs = attr_keys.to_h { |k| [k, options.delete(k)] }
70
+ merged = Poetry::Core::HTML::Attributes.merged(wiring, caller_attrs)
71
+ Button::Component.new(variant: :outline, size: :sm, **merged.symbolize_keys, **options, &block)
72
+ }
73
+
74
+ # APG timing wiring: hover and focus-within hold the timer (the
75
+ # window-blur / tab-hidden holds are wired by the controller).
76
+ use_stimulus do
77
+ on :root do
78
+ controller :toast do
79
+ register
80
+ value :duration, from: :effective_duration
81
+ value :politeness
82
+ action :pause, on: %i[mouseenter focusin]
83
+ action :resume, on: %i[mouseleave focusout]
84
+ end
85
+ end
86
+ on :action do
87
+ controller :toast do
88
+ target :action
89
+ action :dismiss, on: :click
90
+ end
91
+ end
92
+ on :close do
93
+ controller :toast do
94
+ target :close
95
+ action :dismiss, on: :click
96
+ end
97
+ end
98
+ end
99
+
100
+ style :variant, default: :default, required: true, variants: VARIANTS,
101
+ doc: "The intent axis: it picks the icon, and :destructive announces assertively while " \
102
+ ":loading defaults to persistent."
103
+
104
+ option :duration, :integer,
105
+ doc: "nil = derived: 5000ms, or PERSISTENT when an action slot is present (the missable-undo guard). " \
106
+ "<= 0 = persistent."
107
+ option :politeness, :symbol, default: -> { variant == :destructive ? :assertive : :polite },
108
+ doc: "Derived from the variant: destructive announces assertively."
109
+ option :show_close_button, :boolean, default: true,
110
+ doc: "The corner dismiss button - named as the dialog family names it."
111
+
112
+ validates :politeness, inclusion: { in: POLITENESS }
113
+
114
+ part "toast", "The notification item itself (<li>, role=status) - variant, open state, " \
115
+ "and the toaster's stack facts all ride here",
116
+ states: {
117
+ "data-open" => "toast is showing (the server-rendered state; the dismiss exit " \
118
+ "flips the pair before removal)",
119
+ "data-closed" => "toast is animating out",
120
+ "data-variant" => { condition: "always - the resolved variant",
121
+ values: VARIANTS.map(&:to_s) },
122
+ "data-queued" => "the toaster holds it hidden past the visible limit " \
123
+ "(timer paused until a slot frees up)"
124
+ },
125
+ vars: {
126
+ "--poetry-toast-index" => "stack position written by the toaster's reflow " \
127
+ "(newest visible toast = 0)"
128
+ }
129
+ part "toast-icon", "The variant's icon well (aria-hidden; the default variant " \
130
+ "renders none)"
131
+ part "toast-title", "The message - the announced payload's first line (required slot)"
132
+ part "toast-description", "Supporting copy under the title"
133
+
134
+ # @api private
135
+ def before_render
136
+ raise ArgumentError, "Toast requires with_title (the message)" unless title?
137
+ end
138
+
139
+ # @api private
140
+ def effective_duration
141
+ return duration unless duration.nil?
142
+
143
+ # Action-bearing toasts are persistent (a missable undo is a
144
+ # bug); so are loading toasts - the promise recipe REPLACES
145
+ # them (turbo_stream.replace on the toast id) when the job
146
+ # settles, and an auto-dismissed loading state is a lie.
147
+ action? || variant == :loading ? 0 : 5000
148
+ end
149
+
150
+ # @api private
151
+ def variant_icon
152
+ VARIANT_ICONS[variant]
153
+ end
154
+
155
+ # @api private
156
+ def root_attributes
157
+ html_attributes.merge_if_not_set(
158
+ {
159
+ "data-slot" => "toast", "data-variant" => variant, "data-open" => "",
160
+ # aria-live=off ON PURPOSE: the announce singleton does the
161
+ # talking, exactly once (an item that is itself a live
162
+ # region would announce a second time).
163
+ "role" => "status", "aria-live" => "off", "aria-atomic" => "true",
164
+ "tabindex" => "0"
165
+ }.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
166
+ )
167
+ end
168
+
169
+ # @api private
170
+ def close_button_attributes
171
+ stimulus_attributes_for(:close).merge("data-slot" => "toast-close")
172
+ end
173
+
174
+ private :effective_duration, :variant_icon, :root_attributes, :close_button_attributes
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Toast
6
+ # The Toast preview matrix: every variant (each with its icon +
7
+ # politeness derivation), the description pairing, the
8
+ # action-bearing (persistent) undo shape, and a persistent error.
9
+ class Preview < Poetry::Core::Preview::Base
10
+ def default
11
+ toast_example(:default, "Event scheduled", "Friday, July 10 at 5:00 PM")
12
+ end
13
+
14
+ def success
15
+ toast_example(:success, "Changes saved", "Your profile has been updated.")
16
+ end
17
+
18
+ def info
19
+ toast_example(:info, "Heads up", "A new version is available.")
20
+ end
21
+
22
+ def warning
23
+ toast_example(:warning, "Storage almost full", "You have used 90% of your quota.")
24
+ end
25
+
26
+ # Destructive announces ASSERTIVELY (politeness derives from the
27
+ # variant) - failures the user must hear about.
28
+ def destructive
29
+ toast_example(:destructive, "Payment failed", "Your card was declined.")
30
+ end
31
+
32
+ # An action-bearing toast defaults to PERSISTENT (duration nil ->
33
+ # 0): a missable undo is a bug.
34
+ # The promise-lifecycle opener: a spinning persistent toast the
35
+ # job later REPLACES (turbo_stream.replace on its id) with the
36
+ # settled success/destructive toast.
37
+ def loading
38
+ render_component(variant: :loading) do |toast|
39
+ toast.with_title { "Creating event…" }
40
+ end
41
+ end
42
+
43
+ def with_action
44
+ render_component do |toast|
45
+ toast.with_title { "Message deleted" }
46
+ toast.with_action { "Undo" }
47
+ end
48
+ end
49
+
50
+ # Explicit duration: 0 keeps a plain toast on screen until closed.
51
+ def persistent
52
+ render_component(variant: :destructive, duration: 0) do |toast|
53
+ toast.with_title { "Sync failed" }
54
+ toast.with_description { "Changes will retry when you're back online." }
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def toast_example(variant, title, description)
61
+ render_component(variant: variant) do |toast|
62
+ toast.with_title { title }
63
+ toast.with_description { description }
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Toast
6
+ # Re-expressed through the cn-* theme layer - poetry's own visual
7
+ # (no upstream treatment to mirror). The corner-aware slide chains
8
+ # ride the theme with the rest of the treatment (they key on the
9
+ # TOASTER's data-position through group/toaster at runtime); the
10
+ # default variant stays an empty string (the base IS the default -
11
+ # nothing to name).
12
+ class Style < Poetry::Core::Style
13
+ base "cn-toast pointer-events-auto relative flex w-full items-start outline-hidden"
14
+
15
+ variant :variant, {
16
+ default: "",
17
+ success: "cn-toast-variant-success",
18
+ info: "cn-toast-variant-info",
19
+ warning: "cn-toast-variant-warning",
20
+ destructive: "cn-toast-variant-destructive"
21
+ }
22
+
23
+ element :icon, "cn-toast-icon flex shrink-0 items-center justify-center " \
24
+ "[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none"
25
+ element :body, "cn-toast-body flex min-w-0 flex-1 flex-col"
26
+ element :title, "cn-toast-title font-medium text-sm"
27
+ element :description, "cn-toast-description text-muted-foreground text-sm"
28
+ element :action, "shrink-0 self-start"
29
+ element :close, "shrink-0 self-start"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,12 @@
1
+ <div class="p-4">
2
+ <%= render Poetry::Ui::ToastTrigger::Component.new(template: "addressed-preview-toast",
3
+ toaster: "preview-region") do %>
4
+ Show in region
5
+ <% end %>
6
+ </div>
7
+ <template id="addressed-preview-toast">
8
+ <%= poetry_toast(duration: 4000) do |toast| %>
9
+ <% toast.with_title { "Addressed stamp" } %>
10
+ <% end %>
11
+ </template>
12
+ <%= poetry_toaster(id: "preview-region") %>
@@ -0,0 +1,3 @@
1
+ <%= render Poetry::Ui::Button::Component.new(**button_options) do %>
2
+ <%= content %>
3
+ <% end %>
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Client-side toast delivery.
6
+ module ToastTrigger
7
+ # A button that shows a toast without a server round-trip: on press
8
+ # the toaster stamps a clone of an addressed <template>'s toast
9
+ # into its region. The template holds one fully rendered
10
+ # poetry_toast - exactly what a Turbo Stream would append - so the
11
+ # visible limit, queueing, and stacking behave the same on either
12
+ # path. For purely client-side moments: copied, undone, queued.
13
+ #
14
+ # @example
15
+ # <%= render Poetry::Ui::ToastTrigger::Component.new(template: "copied-toast") do %>
16
+ # Copy link
17
+ # <% end %>
18
+ # <template id="copied-toast">
19
+ # <%= poetry_toast(duration: 4000) do |toast| %>
20
+ # <% toast.with_title { "Copied" } %>
21
+ # <% end %>
22
+ # </template>
23
+ class Component < Poetry::Core::Component
24
+ # Projected into the registry, llms.txt, and the agent surface.
25
+ AGENT_RULES = [
26
+ "template: names a <template> element id holding ONE rendered poetry_toast - " \
27
+ "the trigger stamps a clone into the toaster on press.",
28
+ "toaster: scopes the stamp to one region id on multi-toaster pages; omit it for " \
29
+ "the page's toaster.",
30
+ "Give the templated toast a duration (auto-dismiss) - repeated presses stack " \
31
+ "persistent toasts.",
32
+ "Server round-trips keep using turbo_stream.poetry_toast - this trigger is for " \
33
+ "purely client-side moments (copied, undone, queued)."
34
+ ].freeze
35
+
36
+ use_stimulus do
37
+ on :root do
38
+ controller :"toast-trigger" do
39
+ register
40
+ value :template
41
+ value :toaster, if: -> { toaster.present? }
42
+ action :fire, on: :click
43
+ end
44
+ end
45
+ end
46
+
47
+ option :template, :string, required: true,
48
+ doc: "The id of the <template> element holding the rendered toast to stamp."
49
+ option :toaster, :string,
50
+ doc: "Scopes the stamp to one toaster region id on multi-toaster pages; omit for the page's toaster."
51
+ option :variant, :symbol, default: :outline, doc: "The Button variant the trigger renders at."
52
+ option :size, :symbol, default: :default, doc: "The Button size the trigger renders at."
53
+
54
+ part "toast-trigger", "The stamping button - press clones the template's toast " \
55
+ "into the toaster region",
56
+ states: {
57
+ "data-variant" => "always - the Button variant the trigger renders at",
58
+ "data-size" => "always - the Button size the trigger renders at"
59
+ }
60
+ # The trigger IS a poetry Button (no wrapper element), so Button's
61
+ # inner anatomy lands in this component's data-component scope.
62
+ part "label", "The Button's label span (the trigger renders AS a poetry Button)"
63
+
64
+ # @api private
65
+ def button_options
66
+ wiring = { data: { slot: "toast-trigger" } }
67
+ .merge(stimulus_attributes_for(:root))
68
+ .merge(component_data_attributes)
69
+
70
+ # Caller attributes never clobber the trigger's wiring: both sides
71
+ # flow through Attributes, so a host data-controller/action
72
+ # concatenates instead of silently killing fire-on-click.
73
+ { variant: variant, size: size }
74
+ .merge(Poetry::Core::HTML::Attributes.merged(wiring, html_attributes))
75
+ .symbolize_keys
76
+ end
77
+
78
+ private :button_options
79
+ end
80
+ end
81
+ end
82
+ end