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,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Menubar
6
+ # Re-expressed through the cn-* theme layer, the source quirks
7
+ # preserved in the theme rules verbatim (content omits
8
+ # data-closed:animate-out, checkbox/radio round with rounded-xs, the
9
+ # sub-trigger has no [&_svg] block). :menu keeps display:contents
10
+ # inline - the one poetry addition (controller host erased from
11
+ # layout + the a11y tree).
12
+ class Style < Poetry::Core::Style
13
+ base "cn-menubar flex items-center"
14
+
15
+ # POETRY ADDITION: the logical Menu grouping needs a host element
16
+ # for its behavior wiring; display:contents erases it from layout
17
+ # and the a11y tree (the source anatomy renders no element here).
18
+ element :menu, "contents"
19
+
20
+ element :trigger, "cn-menubar-trigger flex items-center outline-hidden select-none"
21
+
22
+ element :content, "cn-menubar-content cn-menu-translucent z-50 " \
23
+ "origin-(--transform-origin) overflow-hidden"
24
+
25
+ # group/menubar-item: bare marker, no CSS - the vega theme's
26
+ # shortcut re-color (group-focus/menubar-item) keys on it.
27
+ element :item, "cn-menubar-item group/menubar-item relative flex cursor-default items-center " \
28
+ "outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0 " \
29
+ "data-disabled:opacity-50 data-disabled:pointer-events-none"
30
+
31
+ element :checkbox_item, "cn-menubar-checkbox-item relative flex cursor-default items-center outline-hidden " \
32
+ "select-none [&_svg]:pointer-events-none [&_svg]:shrink-0 data-disabled:opacity-50 " \
33
+ "data-disabled:pointer-events-none"
34
+
35
+ element :radio_item, "cn-menubar-radio-item relative flex cursor-default items-center outline-hidden " \
36
+ "select-none [&_svg]:pointer-events-none [&_svg]:shrink-0 data-disabled:opacity-50 " \
37
+ "data-disabled:pointer-events-none"
38
+
39
+ element :label, "cn-menubar-label"
40
+
41
+ element :separator, "cn-menubar-separator -mx-1 h-px my-1"
42
+
43
+ element :shortcut, "cn-menubar-shortcut"
44
+
45
+ # DELTA vs the family: outline-none (not -hidden), no [&_svg] block
46
+ # (source-exact).
47
+ element :sub_trigger, "cn-menubar-sub-trigger flex cursor-default items-center " \
48
+ "outline-none select-none"
49
+
50
+ element :sub_content, "cn-menubar-sub-content z-50 " \
51
+ "origin-(--transform-origin) overflow-hidden"
52
+
53
+ # Source-exact wrapper span (anonymous in the ported source; poetry names
54
+ # it menubar-item-indicator - the self-identification rule).
55
+ element :item_indicator, "pointer-events-none absolute " \
56
+ "flex items-center justify-center"
57
+
58
+ # POETRY ADDITION (family convention): the indicator stays in the
59
+ # DOM; the parent item's data-checked/data-unchecked pair drives
60
+ # visibility.
61
+ element :item_indicator_state, "[[data-unchecked]>&]:hidden"
62
+
63
+ # The source's inline lucide icon classes, named per part. DELTA:
64
+ # the menubar sub chevron is ml-auto h-4 w-4 (source-exact).
65
+ element :indicator_check, "size-4"
66
+ element :indicator_circle, "size-2 fill-current"
67
+ element :sub_indicator, "ml-auto h-4 w-4"
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,386 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Gives a menu family (DropdownMenu, ContextMenu, Menubar) one shared
6
+ # implementation of the menu item union and its anatomy classes.
7
+ # Family identity - the data-slot prefix, the Style dictionary, the
8
+ # error noun, the builder classes - resolves through the including
9
+ # family's namespace at render time, never through constants named
10
+ # here, so one implementation serves every family. Each family keeps
11
+ # a thin ItemSlots module carrying its own SLOT_BUILDERS and, where
12
+ # needed, a per-kind indicator override.
13
+ module Menus
14
+ # The closed vocabulary for the item variant axis.
15
+ ITEM_VARIANTS = %i[default destructive].freeze
16
+
17
+ # Shared attribute builders for the item union - mixed into every
18
+ # menu level (the family roots and the nested Sub / Group /
19
+ # RadioGroup parts) so the whole family renders the same anatomy
20
+ # through the same Builder.
21
+ module Helpers
22
+ include FamilyIdentity
23
+
24
+ private
25
+
26
+ # menu items are role=menuitem DIVs (APG-exact) - no native
27
+ # disabled, so aria-disabled and data-disabled are written TOGETHER
28
+ # (the controller filters on either; CSS styles data-[disabled]).
29
+ def apply_item_flags(attrs, inset: false, disabled: false, text_value: nil, close_on_select: nil)
30
+ attrs["data-inset"] = "true" if inset
31
+ if disabled
32
+ attrs["aria-disabled"] = "true"
33
+ attrs["data-disabled"] = ""
34
+ end
35
+ attrs["data-text-value"] = text_value if text_value
36
+ attrs["data-close-on-select"] = close_on_select.to_s unless close_on_select.nil?
37
+ attrs
38
+ end
39
+
40
+ # Every menu item's activation, via the public escape hatch (each
41
+ # family Component's :item declaration mirrors it; the contract
42
+ # gate keeps the two in sync).
43
+ def item_action_attributes
44
+ stimulus_attributes(:menu) { |menu| menu.with_action(:activate, on: :click) }
45
+ end
46
+
47
+ # The named check/circle glyph wrapper (the source's two slots:
48
+ # <family>-checkbox-item-indicator / <family>-radio-item-indicator).
49
+ # State is carried by
50
+ # the parent item's aria-checked/data-checked pair; the glyph
51
+ # itself stays decorative (Icon defaults to aria-hidden).
52
+ def item_indicator(icon, icon_class, kind)
53
+ extra = [family_style.css(:item_indicator_state), indicator_extra_class(kind)].compact
54
+ content_tag(:span, "data-slot" => "#{family_slot_prefix}-#{kind}-item-indicator",
55
+ "class" => family_style.css(:item_indicator, class: extra)) do
56
+ render(Icon::Component.new(name: icon, class: icon_class))
57
+ end
58
+ end
59
+
60
+ # The one substantive family fork: Menubar themes its checkbox and
61
+ # radio indicators separately (a per-kind cn class rides along);
62
+ # the siblings share one treatment.
63
+ def indicator_extra_class(_kind)
64
+ nil
65
+ end
66
+
67
+ # A VISUAL keybinding hint only - poetry does not bind the key
68
+ # (family rule), so the span is aria-hidden presentation.
69
+ def shortcut_span(text)
70
+ return if text.blank?
71
+
72
+ content_tag(:span, text, "data-slot" => "#{family_slot_prefix}-shortcut", "aria-hidden" => "true",
73
+ "class" => family_style.css(:shortcut))
74
+ end
75
+ end
76
+
77
+ # The item UNION every menu level accepts: item | checkbox_item |
78
+ # radio_group | label | separator | group | sub - one ordered
79
+ # collection (interleaving preserved), polymorphic setters named per
80
+ # part. Included (through each family's thin ItemSlots) by the family
81
+ # roots, Sub (recursive submenus), and Group.
82
+ module ItemSlots
83
+ extend ActiveSupport::Concern
84
+ include Helpers
85
+
86
+ included do
87
+ renders_many :items,
88
+ doc: "The menu composition API: one ordered items collection accepting seven kinds, " \
89
+ "interleaved in call order - with_item an action row (href: renders it as a real link; " \
90
+ "submit: as a real submit button) with_checkbox_item a toggleable checked/unchecked row " \
91
+ "with_radio_group a single-select scope; add rows inside it via with_radio_item(value:) " \
92
+ "with_label a non-interactive heading for a run of items with_separator a horizontal " \
93
+ "rule between runs with_group semantic grouping around the same union, one level down " \
94
+ "with_sub a nested submenu: its own with_trigger plus the same union, recursively",
95
+ types: {
96
+ item: { renders: ->(**options, &block) { item_part(**options, &block) }, as: :item },
97
+ checkbox_item: {
98
+ renders: ->(**options, &block) { checkbox_item_part(**options, &block) }, as: :checkbox_item
99
+ },
100
+ radio_group: { renders: lambda { |**options|
101
+ family_namespace::RadioGroup.new(**options)
102
+ }, as: :radio_group },
103
+ label: { renders: ->(**options, &block) { label_part(**options, &block) }, as: :label },
104
+ separator: { renders: ->(**options) { separator_part(**options) }, as: :separator },
105
+ group: { renders: lambda { |**options|
106
+ family_namespace::Group.new(dir: menu_dir, **options)
107
+ }, as: :group },
108
+ sub: { renders: lambda { |**options|
109
+ family_namespace::Sub.new(dir: menu_dir, **options)
110
+ }, as: :sub }
111
+ }
112
+ end
113
+
114
+ private
115
+
116
+ # Builds one action row - a role=menuitem div, or a real <a> /
117
+ # submit <button> when href: / submit: is given.
118
+ def item_part(**options, &)
119
+ variant = (options.delete(:variant) || :default).to_sym
120
+ unless ITEM_VARIANTS.include?(variant)
121
+ raise ArgumentError,
122
+ "unknown #{family_name} item variant #{variant.inspect} - known: #{ITEM_VARIANTS.join(", ")}"
123
+ end
124
+
125
+ shortcut = options.delete(:shortcut)
126
+ # A link/submit item IS the interactive element (role=menuitem on the
127
+ # <a> or the <button>), never nested inside a menuitem div - one
128
+ # interactive element, so it keeps the APG contract without tripping
129
+ # nested-interactive a11y. A disabled link/submit drops its target and
130
+ # renders as the plain div (anchors/submit-buttons have no menu meaning
131
+ # once inert).
132
+ href = options.delete(:href)
133
+ external = options.delete(:external)
134
+ submit = options.delete(:submit)
135
+ method = options.delete(:method)
136
+ disabled = options[:disabled]
137
+ attrs = {
138
+ "data-slot" => "#{family_slot_prefix}-item", "role" => "menuitem", "tabindex" => "-1",
139
+ "data-poetry-collection-item" => "", "data-variant" => variant,
140
+ "class" => family_style.css(:item, class: options.delete(:class))
141
+ }.merge(item_action_attributes)
142
+ apply_item_flags(attrs, **options.extract!(:inset, :disabled, :text_value, :close_on_select))
143
+ content = safe_join([capture(&), shortcut_span(shortcut)].compact)
144
+
145
+ if submit && !disabled
146
+ # form: is RESERVED on submit items - the display:contents form
147
+ # IS the a11y mechanism (the button is the menuitem).
148
+ options.delete(:form)
149
+ options.delete("form")
150
+ # button_to's form is display:contents (transparent); the submit
151
+ # button IS the menuitem, POSTing with CSRF + the method override -
152
+ # the a11y-clean way to run a DELETE/POST action from a menu. The
153
+ # block form renders a <button> (the string form renders an <input>).
154
+ return helpers.button_to(submit,
155
+ { method: method || :post, form: { class: "contents" } }
156
+ .merge(Poetry::Core::HTML::Attributes.merged(attrs, options))) { content }
157
+ end
158
+
159
+ link = href && !disabled
160
+ if link
161
+ attrs["href"] = href
162
+ if external
163
+ attrs["target"] = "_blank"
164
+ attrs["rel"] = "noopener noreferrer"
165
+ end
166
+ end
167
+ content_tag(link ? :a : :div, Poetry::Core::HTML::Attributes.merged(attrs, options)) { content }
168
+ end
169
+
170
+ # Builds one role=menuitemcheckbox toggle row.
171
+ def checkbox_item_part(**options, &)
172
+ checked = options.delete(:checked) || false
173
+ shortcut = options.delete(:shortcut)
174
+ attrs = {
175
+ "data-slot" => "#{family_slot_prefix}-checkbox-item", "role" => "menuitemcheckbox", "tabindex" => "-1",
176
+ "data-poetry-collection-item" => "",
177
+ # aria-checked and the data-checked/data-unchecked pair written
178
+ # TOGETHER, never separately.
179
+ "aria-checked" => checked.to_s, (checked ? "data-checked" : "data-unchecked") => "",
180
+ "class" => family_style.css(:checkbox_item, class: options.delete(:class))
181
+ }.merge(item_action_attributes)
182
+ apply_item_flags(attrs, **options.extract!(:disabled, :text_value, :close_on_select))
183
+ content_tag(:div, Poetry::Core::HTML::Attributes.merged(attrs, options)) do
184
+ safe_join([item_indicator(:check, family_style.css(:indicator_check), :checkbox),
185
+ capture(&), shortcut_span(shortcut)].compact)
186
+ end
187
+ end
188
+
189
+ # Builds the non-interactive heading row.
190
+ def label_part(inset: false, **options, &)
191
+ attrs = {
192
+ "data-slot" => "#{family_slot_prefix}-label",
193
+ "class" => family_style.css(:label, class: options.delete(:class))
194
+ }
195
+ attrs["data-inset"] = "true" if inset
196
+ content_tag(:div, Poetry::Core::HTML::Attributes.merged(attrs, options)) { capture(&) }
197
+ end
198
+
199
+ # Builds the role=separator rule.
200
+ def separator_part(**options)
201
+ attrs = {
202
+ "data-slot" => "#{family_slot_prefix}-separator", "role" => "separator",
203
+ "aria-orientation" => "horizontal",
204
+ "class" => family_style.css(:separator, class: options.delete(:class))
205
+ }
206
+ content_tag(:div, nil, Poetry::Core::HTML::Attributes.merged(attrs, options))
207
+ end
208
+ end
209
+
210
+ # role=group semantic grouping between separators - the same item
211
+ # union, one level down.
212
+ #
213
+ # @api private
214
+ class Group < Poetry::Core::Component
215
+ internal_component!
216
+ include ItemSlots
217
+
218
+ def initialize(dir: nil, **extra_attributes)
219
+ super(extra_attributes)
220
+ @dir = dir
221
+ end
222
+
223
+ def before_render
224
+ raise ArgumentError, "#{family_name} group requires at least one item" unless items?
225
+ end
226
+
227
+ def call
228
+ attrs = { "data-slot" => "#{family_slot_prefix}-group", "role" => "group" }
229
+ content_tag(:div, Poetry::Core::HTML::Attributes.merged(attrs, html_attributes)) { safe_join(items.map(&:to_s)) }
230
+ end
231
+
232
+ private
233
+
234
+ def menu_dir
235
+ @dir
236
+ end
237
+ end
238
+
239
+ # role=group scoping the single-select value for its radio items -
240
+ # radio items exist only through this group; duplicate values raise.
241
+ #
242
+ # @api private
243
+ class RadioGroup < Poetry::Core::Component
244
+ internal_component!
245
+ include Helpers
246
+
247
+ attr_reader :group_value
248
+
249
+ renders_many :radio_items,
250
+ doc: "One role=menuitemradio row; value: must be unique within the group.",
251
+ renders: lambda { |value:, disabled: false, text_value: nil,
252
+ close_on_select: nil, shortcut: nil, **options, &block|
253
+ key = value.to_s
254
+ unless @seen_values.add?(key)
255
+ raise ArgumentError, "duplicate #{family_name} radio value #{key.inspect} - values must be " \
256
+ "unique within their radio group"
257
+ end
258
+
259
+ checked = !group_value.nil? && key == group_value
260
+ attrs = {
261
+ "data-slot" => "#{family_slot_prefix}-radio-item", "role" => "menuitemradio",
262
+ "tabindex" => "-1", "data-poetry-collection-item" => "", "data-value" => key,
263
+ "aria-checked" => checked.to_s, (checked ? "data-checked" : "data-unchecked") => "",
264
+ "class" => family_style.css(:radio_item, class: options.delete(:class))
265
+ }.merge(item_action_attributes)
266
+ apply_item_flags(attrs, disabled:, text_value:, close_on_select:)
267
+ content_tag(:div, Poetry::Core::HTML::Attributes.merged(attrs, options)) do
268
+ safe_join([item_indicator(:circle, family_style.css(:indicator_circle), :radio),
269
+ capture(&block), shortcut_span(shortcut)].compact)
270
+ end
271
+ }
272
+
273
+ def initialize(value: nil, **extra_attributes)
274
+ super(extra_attributes)
275
+ @group_value = value&.to_s
276
+ @seen_values = Set.new
277
+ end
278
+
279
+ def before_render
280
+ raise ArgumentError, "#{family_name} radio group requires at least one with_radio_item" unless radio_items?
281
+ end
282
+
283
+ def call
284
+ attrs = { "data-slot" => "#{family_slot_prefix}-radio-group", "role" => "group" }
285
+ attrs["data-value"] = group_value if group_value
286
+ content_tag(:div, Poetry::Core::HTML::Attributes.merged(attrs, html_attributes)) { safe_join(radio_items.map(&:to_s)) }
287
+ end
288
+ end
289
+
290
+ # A submenu scope - its own positioning instance (trigger anchors
291
+ # content; side flips under RTL) around the same item union, recursively.
292
+ #
293
+ # @api private
294
+ class Sub < Poetry::Core::Component
295
+ internal_component!
296
+ include ItemSlots
297
+
298
+ renders_one :trigger,
299
+ doc: "role=menuitem in the PARENT's collection + aria wiring to its own sub-content; the " \
300
+ "trailing chevron ships built in (flips via logical ml-auto under RTL).",
301
+ renders: lambda { |inset: false, disabled: false, text_value: nil, **options, &block|
302
+ attrs = {
303
+ "id" => trigger_id, "data-slot" => "#{family_slot_prefix}-sub-trigger", "role" => "menuitem",
304
+ "tabindex" => "-1", "data-poetry-collection-item" => "",
305
+ "aria-haspopup" => "menu", "aria-expanded" => "false", "aria-controls" => content_id,
306
+ "class" => family_style.css(:sub_trigger, class: options.delete(:class))
307
+ }.merge(sub_trigger_stimulus_attributes)
308
+ apply_item_flags(attrs, inset:, disabled:, text_value:)
309
+ content_tag(:div, Poetry::Core::HTML::Attributes.merged(attrs, options)) do
310
+ safe_join([capture(&block), chevron])
311
+ end
312
+ }
313
+
314
+ def initialize(dir: nil, **extra_attributes)
315
+ super(extra_attributes)
316
+ @dir = dir
317
+ end
318
+
319
+ def before_render
320
+ raise ArgumentError, "#{family_name} sub requires with_trigger (the sub-menu item)" unless trigger?
321
+ raise ArgumentError, "#{family_name} sub requires at least one item" unless items?
322
+ end
323
+
324
+ def call
325
+ content_tag(:div, sub_attributes) { safe_join([trigger, sub_content]) }
326
+ end
327
+
328
+ def trigger_id
329
+ "#{instance_id}-trigger"
330
+ end
331
+
332
+ def content_id
333
+ "#{instance_id}-content"
334
+ end
335
+
336
+ private
337
+
338
+ def menu_dir
339
+ @dir
340
+ end
341
+
342
+ def rtl?
343
+ @dir == :rtl
344
+ end
345
+
346
+ def instance_id
347
+ @instance_id ||= poetry_instance_id("poetry-#{family_slot_prefix}-sub")
348
+ end
349
+
350
+ def sub_attributes
351
+ attrs = { "data-slot" => "#{family_slot_prefix}-sub" }.merge(
352
+ stimulus_attributes(:popper) do |popper|
353
+ popper.register_controller
354
+ popper.with_value(:side, rtl? ? :left : :right)
355
+ popper.with_value(:align, :start)
356
+ end
357
+ )
358
+ Poetry::Core::HTML::Attributes.merged(attrs, html_attributes)
359
+ end
360
+
361
+ def sub_content
362
+ attrs = {
363
+ "id" => content_id, "role" => "menu", "aria-orientation" => "vertical",
364
+ "aria-labelledby" => trigger_id, "tabindex" => "-1",
365
+ "data-slot" => "#{family_slot_prefix}-sub-content", "data-closed" => "", "hidden" => true,
366
+ "class" => family_style.css(:sub_content)
367
+ }.merge(stimulus_attributes(:popper) { |popper| popper.with_target(:content) })
368
+ content_tag(:div, attrs) { safe_join(items.map(&:to_s)) }
369
+ end
370
+
371
+ def sub_trigger_stimulus_attributes
372
+ stimulus_attributes(:menu, :popper) do |menu, popper|
373
+ menu.with_action(:sub_enter, on: :pointerenter)
374
+ menu.with_action(:sub_leave, on: :pointerleave)
375
+ menu.with_action(:open_sub, on: :click)
376
+ popper.with_target(:anchor)
377
+ end
378
+ end
379
+
380
+ def chevron
381
+ render(Icon::Component.new(name: :"chevron-right", class: family_style.css(:sub_indicator)))
382
+ end
383
+ end
384
+ end
385
+ end
386
+ end
@@ -0,0 +1,8 @@
1
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
2
+ <% if avatar? %><div data-slot="message-avatar" class="<%= css(:avatar) %>"><%= avatar %></div><% end %>
3
+ <div data-slot="message-content" class="<%= css(:content) %>">
4
+ <% if header? %><div data-slot="message-header" class="<%= css(:header) %>"><%= header %></div><% end %>
5
+ <%= content %>
6
+ <% if footer? %><div data-slot="message-footer" class="<%= css(:footer) %>"><%= footer %></div><% end %>
7
+ </div>
8
+ <% end %>
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # One chat turn's row in a conversation transcript.
6
+ module Message
7
+ # One chat turn's row: an avatar beside a content column of
8
+ # header, message bubbles (the body block), and footer. align:
9
+ # :end mirrors the row for the local user's side. Purely
10
+ # presentational - no JavaScript.
11
+ #
12
+ # @example
13
+ # render Poetry::Ui::Message::Component.new do |message|
14
+ # message.with_avatar { "AI" }
15
+ # message.with_header { "Assistant" }
16
+ # tag.div("Here's the plan for today.")
17
+ # end
18
+ class Component < Poetry::Core::Component
19
+ # The closed vocabulary for the align axis.
20
+ ALIGNS = %i[start end].freeze
21
+
22
+ # Projected into the registry, llms.txt, and the agent surface.
23
+ AGENT_RULES = [
24
+ "One Message per turn: avatar slot + header/footer slots; the body block holds the Bubbles.",
25
+ "align: :end is the local user's side - set it on the Message, never on the Bubbles inside.",
26
+ "The avatar slot is decorative context by default - pass meaningful sender identity in the header.",
27
+ "Timestamps and delivery state belong in the footer slot (it lifts the avatar automatically)."
28
+ ].freeze
29
+
30
+ renders_one :avatar, doc: "The sender's avatar, kept beside the content column - decorative context; put " \
31
+ "meaningful sender identity in the header."
32
+ renders_one :header, doc: "The sender identity line above the bubbles."
33
+ renders_one :footer, doc: "Timestamps / delivery state below the bubbles."
34
+
35
+ option :align, :symbol, default: :start,
36
+ doc: "Which side the row sits on; :end mirrors it for the local user's side."
37
+
38
+ validates :align, inclusion: { in: ALIGNS }
39
+
40
+ part "message", "The chat-row root - avatar plus a content column; align: :end " \
41
+ "mirrors the row for the local user's side",
42
+ states: {
43
+ "data-align" => { condition: "always - the resolved align",
44
+ values: ALIGNS.map(&:to_s) }
45
+ }
46
+ part "message-avatar", "The avatar slot's box, kept out of the content column"
47
+ part "message-content", "The content column - header, the body block (the Bubbles), " \
48
+ "footer"
49
+ part "message-header", "Sender identity line above the bubbles (header slot)"
50
+ part "message-footer", "Timestamps / delivery state below the bubbles (footer slot - " \
51
+ "it lifts the avatar)"
52
+
53
+ # @api private
54
+ def root_attributes
55
+ html_attributes.merge_if_not_set(
56
+ { "data-slot" => "message", "data-align" => align }.merge(component_data_attributes)
57
+ )
58
+ end
59
+
60
+ private :root_attributes
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Message
6
+ # The Message preview matrix.
7
+ class Preview < Poetry::Core::Preview::Base
8
+ def default
9
+ render_component do |message|
10
+ message.with_avatar { "AI" }
11
+ message.with_header { "Assistant" }
12
+ tag.div("Here's the plan for today.", class: "rounded-xl bg-muted px-3 py-2 w-fit")
13
+ end
14
+ end
15
+
16
+ def align_end
17
+ render_component(align: :end) do |message|
18
+ message.with_footer { "Read 10:42" }
19
+ tag.div("Sounds good!", class: "rounded-xl bg-primary text-primary-foreground px-3 py-2 w-fit")
20
+ end
21
+ end
22
+
23
+ def with_footer_lifts_avatar
24
+ render_component do |message|
25
+ message.with_avatar { "AI" }
26
+ message.with_footer { "10:41" }
27
+ tag.div("Footer present - the avatar lifts.", class: "rounded-xl bg-muted px-3 py-2 w-fit")
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Message
6
+ # Re-expressed through the cn-* theme layer. The :has()/group
7
+ # context choreography stays inline (footer presence lifts the
8
+ # avatar; the align axis flips rows and self-alignment); type,
9
+ # spacing, and the ghost-Bubble padding collapse ride the theme.
10
+ class Style < Poetry::Core::Style
11
+ base "cn-message group/message relative flex w-full min-w-0 data-[align=end]:flex-row-reverse"
12
+
13
+ element :group, "cn-message-group flex min-w-0 flex-col"
14
+
15
+ element :avatar, "cn-message-avatar flex w-fit shrink-0 items-center justify-center self-end " \
16
+ "overflow-hidden rounded-full bg-muted " \
17
+ "group-has-data-[slot=message-footer]/message:-translate-y-8"
18
+
19
+ element :content, "cn-message-content flex w-full min-w-0 flex-col wrap-break-word " \
20
+ "group-data-[align=end]/message:*:data-slot:self-end"
21
+
22
+ element :header, "cn-message-header flex max-w-full min-w-0 items-center"
23
+
24
+ element :footer, "cn-message-footer flex max-w-full min-w-0 items-center " \
25
+ "group-data-[align=end]/message:justify-end"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
2
+ <div <%= tag.attributes(viewport_attributes) %>>
3
+ <div <%= tag.attributes(content_attributes) %>>
4
+ <%= content %>
5
+ <div <%= tag.attributes(spacer_attributes) %>></div>
6
+ </div>
7
+ </div>
8
+ <% if jump_button %>
9
+ <%= render Poetry::Ui::Button::Component.new(variant: :secondary, size: :"icon-sm",
10
+ label: t("poetry.message_scroller.scroll_to_end"),
11
+ **button_attributes) do %>
12
+ <%= render Poetry::Ui::Icon::Component.new(name: :"arrow-down") %>
13
+ <% end %>
14
+ <% end %>
15
+ <% end %>