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,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # A click-opened panel anchored to its trigger.
6
+ module Popover
7
+ # The placement vocabularies - the popper-consumer kit owns them.
8
+ SIDES = Poetry::Ui::PopperConsumer::SIDES
9
+ # The closed vocabulary for the align axis.
10
+ ALIGNS = Poetry::Ui::PopperConsumer::ALIGNS
11
+
12
+ # A click-opened role=dialog panel anchored to its trigger, for
13
+ # interactive content: small forms, filters, pickers. Opening
14
+ # moves focus to the first tabbable element in the panel; Escape
15
+ # or outside interaction closes it and returns focus to the
16
+ # trigger. Non-modal by default - the rest of the page stays
17
+ # interactive while it is open.
18
+ #
19
+ # with_trigger composes a Button as the control. Name the panel
20
+ # via with_title (preferred) or label: - a dialog needs an
21
+ # accessible name. The focus and dismiss behavior activates only
22
+ # while open, so closed popovers cost nothing at page load.
23
+ #
24
+ # @example
25
+ # render Poetry::Ui::Popover::Component.new do |popover|
26
+ # popover.with_trigger(variant: :outline) { "Open popover" }
27
+ # popover.with_title { "Dimensions" }
28
+ # tag.p("Set the dimensions for the layer.")
29
+ # end
30
+ class Component < Poetry::Core::Component
31
+ include Poetry::Ui::ComposableTrigger
32
+ include Poetry::Ui::PopperConsumer
33
+
34
+ # Projected into the registry, llms.txt, and the agent surface.
35
+ AGENT_RULES = [
36
+ ComposableTrigger::AGENT_RULE,
37
+ "Use poetry_popover - never hand-roll an anchored role=dialog panel with Tailwind.",
38
+ "Popover content is INTERACTIVE - for text-only hover hints use Tooltip; for pointer-only " \
39
+ "previews use HoverCard.",
40
+ "Give the panel a name: use with_title (preferred) or label: - a role=dialog without a name " \
41
+ "fails the audit.",
42
+ "Icon-only triggers MUST have an accessible name (the composed Button's label: rule).",
43
+ "Default is NON-modal (modal: false) - reach for modal: true only when stray outside " \
44
+ "interaction would corrupt the task; reach for Dialog when the task deserves full modality.",
45
+ "Critical-path panels must also be reachable without JS (full page or server-rendered " \
46
+ "open: true) - popovers are JS-required interaction.",
47
+ "Do not nest a Popover inside a Popover - restructure (the layer stack allows it; " \
48
+ "comprehension does not)."
49
+ ].freeze
50
+
51
+ # Slots the component cannot render without; static checks read this without rendering.
52
+ REQUIRED_SLOTS = { trigger: "the panel's control" }.freeze
53
+
54
+ # The class each slot's builder renders - with_trigger composes a
55
+ # Button, so callers get Button's full option contract.
56
+ SLOT_RENDERS = { trigger: Button::Component }.freeze
57
+
58
+ renders_one :trigger,
59
+ doc: "The control that opens the panel - a composed Button. The slot owns the " \
60
+ "aria-haspopup/expanded/controls wiring regardless of the composed content, so composition " \
61
+ "cannot drop the aria; aria-controls renders even while closed (the stable id is the " \
62
+ "wiring's resolution seam).",
63
+ renders: lambda { |**options, &block|
64
+ wiring = {
65
+ "id" => trigger_id, "data-slot" => "popover-trigger",
66
+ "aria-haspopup" => "dialog", "aria-expanded" => open.to_s, "aria-controls" => content_id
67
+ }.merge(stimulus_attributes_for(:trigger))
68
+ # Trigger open state: bare data-popup-open while open, NO
69
+ # attribute while closed (absence IS the state).
70
+ wiring["data-popup-open"] = "" if open
71
+ composed_trigger(wiring, options, &block) ||
72
+ Button::Component.new(**wiring, **options, &block)
73
+ }
74
+
75
+ renders_one :anchor,
76
+ doc: "Optional alternate anchor: when present, the panel positions against IT instead of the " \
77
+ "trigger (targets beat selectors in the positioning fallback chain).",
78
+ renders: lambda { |**options, &block|
79
+ attrs = { "data-slot" => "popover-anchor" }
80
+ .merge(stimulus_attributes_for(:anchor_part))
81
+ content_tag(:div, attrs.merge(options)) { capture(&block) }
82
+ }
83
+
84
+ renders_one :title, doc: "Panel heading - presence wires the content's aria-labelledby, so the title names " \
85
+ "the dialog."
86
+
87
+ renders_one :description, doc: "Supporting text - presence wires the content's aria-describedby."
88
+
89
+ use_stimulus do
90
+ on :root do
91
+ controller :popover do
92
+ register
93
+ value :open
94
+ value :modal
95
+ end
96
+ controller :popper do
97
+ register
98
+ # The trigger anchors by id selector; an anchor part
99
+ # overrides it with the anchor TARGET (render-time decision -
100
+ # slots may be set in any order, so the trigger itself
101
+ # carries no popper target).
102
+ value :anchor, from: :trigger_anchor_selector, unless: :anchor?
103
+ value :side
104
+ value :align
105
+ value :side_offset
106
+ value :align_offset
107
+ value :avoid_collisions
108
+ end
109
+ end
110
+ on :trigger do
111
+ controller(:popover) { action :toggle, on: :click }
112
+ end
113
+ on :anchor_part do
114
+ controller(:popper) { target :anchor }
115
+ end
116
+ on :content do
117
+ controller(:popper) { target :content }
118
+ end
119
+ end
120
+
121
+ option :open, :boolean, default: false, doc: "Server-renders the panel open."
122
+ option :modal, :boolean, default: false,
123
+ doc: "Reserves interaction for the panel while open; the default keeps the rest of " \
124
+ "the page interactive."
125
+ # The placement axes for the anchored panel (side, align, offsets).
126
+ popper_placement_options(side: :bottom, side_offset: 4)
127
+ option :label, :string, doc: "role=dialog fallback name when no title part is present."
128
+ option :content_class, :string,
129
+ doc: "Class merge seam for the panel itself (e.g. widen the default with content_class: \"w-80\") - " \
130
+ "root-level class: styles the wrapper, not the panel."
131
+
132
+ part "popover", "Root wrapper around the trigger, the optional anchor, and the panel"
133
+ part "popover-anchor", "Optional alternate popper anchor - when " \
134
+ "present the panel positions against it instead of the trigger"
135
+ part "popover-content", "The role=dialog panel - positioning, animation, and the open " \
136
+ "state ride here",
137
+ states: {
138
+ "data-open" => "panel is open (the controller flips the pair at runtime)",
139
+ "data-closed" => "panel is closed (the server-rendered state; hidden rides along)",
140
+ "data-side" => { condition: "always - the side (initial placement, re-resolved " \
141
+ "live by popper after flip)",
142
+ values: SIDES.map(&:to_s) },
143
+ "data-align" => { condition: "always - the alignment (re-resolved live by popper)",
144
+ values: ALIGNS.map(&:to_s) }
145
+ },
146
+ vars: Poetry::Ui::PopperConsumer.content_vars
147
+ part "popover-header", "Title block wrapping the title and description (renders only " \
148
+ "when either is present)"
149
+ part "popover-title", "The heading - the panel's accessible name via aria-labelledby"
150
+ part "popover-description", "Muted copy under the title, wired to aria-describedby"
151
+
152
+ # Enforces the required trigger and warns on a nameless dialog.
153
+ # @api private
154
+ def before_render
155
+ raise ArgumentError, "Popover requires with_trigger (the panel's control)" unless trigger?
156
+
157
+ return if title? || label.present?
158
+
159
+ # Lint-level warning, not an error: a role=dialog SHOULD have an
160
+ # accessible name (with_title preferred, label: the fallback).
161
+ Rails.logger&.warn(
162
+ "poetry Popover: role=dialog panel has no accessible name - give it with_title or label:"
163
+ )
164
+ end
165
+
166
+ # @api private
167
+ def title_id
168
+ "#{instance_id}-title"
169
+ end
170
+
171
+ # @api private
172
+ def description_id
173
+ "#{instance_id}-description"
174
+ end
175
+
176
+ # @api private
177
+ def content_attributes
178
+ attrs = {
179
+ "id" => content_id, "role" => "dialog", "tabindex" => "-1",
180
+ "data-slot" => "popover-content", (open ? "data-open" : "data-closed") => "",
181
+ # Initial placement, re-resolved live by popper on open.
182
+ "data-side" => side, "data-align" => align,
183
+ "class" => css(:content, class: content_class)
184
+ }.merge(stimulus_attributes_for(:content))
185
+ if title?
186
+ attrs["aria-labelledby"] = title_id
187
+ elsif label.present?
188
+ attrs["aria-label"] = label
189
+ end
190
+ attrs["aria-describedby"] = description_id if description?
191
+ attrs["hidden"] = true unless open
192
+ attrs
193
+ end
194
+
195
+ private
196
+
197
+ def trigger_anchor_selector = "##{trigger_id}"
198
+
199
+ private :title_id, :description_id, :content_attributes
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,29 @@
1
+ <%# Class-wide sidecar (it displaces poetry-core's generic preview.html.erb
2
+ fallback, so it must keep that contract): examples that pass their own
3
+ composition flow through unchanged; the template-owned examples
4
+ (default/modal) render the upstream popover-demo form - grid gap-2 rows,
5
+ each grid-cols-3 with the input spanning two columns at h-8 - the layout
6
+ the port originally dropped (bare label+input pairs had no spacing). %>
7
+ <div class="<%= defined?(container_class) && container_class.presence || "p-4" %>">
8
+ <% if local_assigns[:content_block] %>
9
+ <%= render(component, &content_block) %>
10
+ <% else %>
11
+ <%= render component do |popover| %>
12
+ <% popover.with_trigger(variant: :outline) { "Open popover" } %>
13
+ <% popover.with_title { "Dimensions" } %>
14
+ <% popover.with_description { "Set the dimensions for the layer." } %>
15
+ <div class="grid gap-2">
16
+ <div class="grid grid-cols-3 items-center gap-4">
17
+ <%= render Poetry::Ui::Label::Component.new(for_id: "popover-demo-width").with_content("Width") %>
18
+ <%= render Poetry::Ui::Input::Component.new(name: "width", value: "100%",
19
+ id: "popover-demo-width", class: "col-span-2 h-8") %>
20
+ </div>
21
+ <div class="grid grid-cols-3 items-center gap-4">
22
+ <%= render Poetry::Ui::Label::Component.new(for_id: "popover-demo-height").with_content("Height") %>
23
+ <%= render Poetry::Ui::Input::Component.new(name: "height", value: "25px",
24
+ id: "popover-demo-height", class: "col-span-2 h-8") %>
25
+ </div>
26
+ </div>
27
+ <% end %>
28
+ <% end %>
29
+ </div>
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Popover
6
+ # The Popover preview matrix: the ported popover-demo composition
7
+ # (form-in-popover, w-80 override) plus both modality modes, the
8
+ # anchor part, the label: naming fallback, a placement sample, and
9
+ # the server-rendered open state.
10
+ class Preview < Poetry::Core::Preview::Base
11
+ # The popover-demo port: outline Button trigger, titled/described
12
+ # panel, the dimensions form in upstream's grid rows (the sidecar
13
+ # preview.html.erb - herb-scanned, so the layout classes reach the
14
+ # safelist), the demo's w-80 class override.
15
+ def default
16
+ render_with(component: Component.new(content_class: "w-80"))
17
+ end
18
+
19
+ # modal: true - focus-scope trapped + the dismissable scrim.
20
+ def modal
21
+ render_with(component: Component.new(content_class: "w-80", modal: true))
22
+ end
23
+
24
+ # The anchor part takes the popper anchor target: the panel
25
+ # positions against IT, not the trigger (the source's anchor part).
26
+ def with_anchor
27
+ render_component do |popover|
28
+ popover.with_trigger(variant: :outline) { "Open popover" }
29
+ popover.with_anchor { "The panel anchors to this text, not the button." }
30
+ popover.with_title { "Anchored" }
31
+ "Positioned against the anchor part."
32
+ end
33
+ end
34
+
35
+ # No title part: label: names the role=dialog via aria-label.
36
+ def named_by_label
37
+ render_component(label: "Quick settings") do |popover|
38
+ popover.with_trigger(variant: :outline) { "Settings" }
39
+ "A panel named by label: instead of a title part."
40
+ end
41
+ end
42
+
43
+ # Server-rendered open state (controllable-state: the attributes
44
+ # are the store; the controller reconciles on connect).
45
+ def open
46
+ render_component(open: true) do |popover|
47
+ popover.with_trigger(variant: :outline) { "Open popover" }
48
+ popover.with_title { "Server-opened" }
49
+ popover.with_description { "Rendered open - fully usable before (or without) JS." }
50
+ "Static content."
51
+ end
52
+ end
53
+
54
+ # Placement sample (side/align flow through to popper; data-side
55
+ # re-resolves live on collision).
56
+ def side_top
57
+ render_component(side: :top, align: :start) do |popover|
58
+ popover.with_trigger(variant: :outline) { "Open above" }
59
+ popover.with_title { "Placement" }
60
+ "side: :top, align: :start"
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Popover
6
+ # Re-expressed through the cn-* theme layer. w-72 stays inline
7
+ # per upstream's split (the caller-overridable width default); the
8
+ # origin class binds through the tokens/aliases.css alias to popper's
9
+ # generic var and stays inline as mechanism.
10
+ class Style < Poetry::Core::Style
11
+ # The panel's DISPLAY + header/body gap are THEME-owned (every
12
+ # theme port ships its own flex-col gap-*; lyra/nova deliberately run
13
+ # tighter at 2.5) - closed panels stay hidden regardless because
14
+ # preflight's [hidden] rule is !important.
15
+ element :content, "cn-popover-content z-50 w-72 " \
16
+ "origin-(--transform-origin) outline-hidden"
17
+
18
+ # Ported-source additions (plain divs there; poetry wires their
19
+ # ids to the dialog's aria-labelledby/describedby).
20
+ element :header, "cn-popover-header flex flex-col"
21
+ element :title, "cn-popover-title"
22
+ element :description, "cn-popover-description"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Gives a trigger-anchored popup component (Popover, HoverCard,
6
+ # Tooltip) the shared positioning surface: the placement vocabulary,
7
+ # the popper_placement_options macro, the content part's CSS-var
8
+ # contract, and the trigger/content id pair. Stimulus declarations
9
+ # stay per-family: each family's value set genuinely differs.
10
+ module PopperConsumer
11
+ extend ActiveSupport::Concern
12
+ include FamilyIdentity
13
+
14
+ # The closed vocabulary for the side placement axis.
15
+ SIDES = %i[top right bottom left].freeze
16
+ # The closed vocabulary for the align placement axis.
17
+ ALIGNS = %i[start center end].freeze
18
+
19
+ class_methods do
20
+ # Declares the five placement options (side, align, side_offset,
21
+ # align_offset, avoid_collisions) plus their vocabulary
22
+ # validations on the including component - each family passes its
23
+ # own defaults.
24
+ def popper_placement_options(side:, side_offset:, align: :center, align_offset: 0)
25
+ option :side, :symbol, default: side, doc: "Which side of the anchor the panel opens on."
26
+ option :align, :symbol, default: align, doc: "Panel alignment along the chosen side."
27
+ option :side_offset, :integer, default: side_offset,
28
+ doc: "Gap in pixels between the anchor and the panel."
29
+ option :align_offset, :integer, default: align_offset,
30
+ doc: "Shift in pixels along the alignment axis."
31
+ option :avoid_collisions, :boolean, default: true,
32
+ doc: "Flips and shifts the panel to stay inside the viewport."
33
+ validates :side, inclusion: { in: SIDES }
34
+ validates :align, inclusion: { in: ALIGNS }
35
+ end
36
+ end
37
+
38
+ # The CSS custom properties the positioning engine writes onto the
39
+ # content part - inlined into a family's part declaration as its
40
+ # vars:. noun names the family's surface in the prose ("panel",
41
+ # "bubble").
42
+ def self.content_vars(noun = "panel")
43
+ {
44
+ "--transform-origin" => "the anchor-facing origin popper writes for scale-in animation",
45
+ "--available-width" => "viewport space left for the #{noun} (popper, post-flip)",
46
+ "--available-height" => "viewport space left for the #{noun} (popper, post-flip)",
47
+ "--anchor-width" => "the anchor's measured width (popper)",
48
+ "--anchor-height" => "the anchor's measured height (popper)"
49
+ }
50
+ end
51
+
52
+ # The trigger element's server-stable id.
53
+ # @api private
54
+ def trigger_id
55
+ "#{instance_id}-trigger"
56
+ end
57
+
58
+ # The content element's server-stable id. The controller resolves
59
+ # content by the id pair ("-trigger" -> "-content"), portal-safe -
60
+ # no Stimulus target.
61
+ # @api private
62
+ def content_id
63
+ "#{instance_id}-content"
64
+ end
65
+
66
+ private :trigger_id, :content_id
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # A determinate progress bar.
6
+ module Progress
7
+ # A determinate task's completion, server-rendered: a
8
+ # role=progressbar root carrying the value in aria, with a filled
9
+ # track sized to the percentage. label: is the accessible name and
10
+ # renders as the visible caption beside a percent readout. For an
11
+ # unknown duration use Spinner - this bar has no indeterminate
12
+ # state.
13
+ #
14
+ # @example
15
+ # render Poetry::Ui::Progress::Component.new(value: 60, label: "Uploading")
16
+ class Component < Poetry::Core::Component
17
+ # Projected into the registry, llms.txt, and the agent surface.
18
+ AGENT_RULES = [
19
+ "label: is REQUIRED - it is the progressbar's accessible name and the visible caption.",
20
+ "value: is the current progress (0..max:, default max 100); the component computes the width.",
21
+ "For an UNKNOWN duration use Spinner, not Progress - this bar is determinate."
22
+ ].freeze
23
+
24
+ option :value, :integer, required: true, doc: "The current progress, clamped into 0..max:."
25
+ option :max, :integer, default: 100, doc: "The completion value."
26
+ option :label, :string, required: true, doc: "The progressbar's accessible name and visible caption."
27
+ option :show_value, :boolean, default: true, doc: "Set false to hide the percent readout."
28
+
29
+ part "progress", "Root (role=progressbar, aria-value* and the accessible name) - " \
30
+ "label, value readout, and track stack here"
31
+ part "progress-label", "The visible caption span (label:)"
32
+ part "progress-value", "The tabular percent readout - renders unless show_value: false"
33
+ part "progress-track", "The full-width rail the indicator fills"
34
+ part "progress-indicator", "The filled bar - sized by an inline width percentage " \
35
+ "computed from value:/max:"
36
+
37
+ # Enforces the required label and a positive max.
38
+ # @api private
39
+ def before_render
40
+ raise ArgumentError, "Progress requires label: (the progressbar's accessible name)" if label.blank?
41
+ raise ArgumentError, "Progress max: must be positive" unless max.positive?
42
+ end
43
+
44
+ # @api private
45
+ def call
46
+ content_tag(:div, root_attributes.to_attributes) do
47
+ safe_join([label_part, value_part, track].compact)
48
+ end
49
+ end
50
+
51
+ # @api private
52
+ def percent
53
+ (value.to_f / max * 100).clamp(0, 100)
54
+ end
55
+
56
+ # @api private
57
+ def percent_text
58
+ "#{percent.round}%"
59
+ end
60
+
61
+ # @api private
62
+ def root_attributes
63
+ html_attributes.merge_if_not_set(
64
+ {
65
+ "data-slot" => "progress", "role" => "progressbar",
66
+ "aria-valuemin" => 0, "aria-valuemax" => max, "aria-valuenow" => value.clamp(0, max),
67
+ "aria-label" => label
68
+ }.merge(component_data_attributes)
69
+ )
70
+ end
71
+
72
+ private
73
+
74
+ def label_part
75
+ content_tag(:span, label, "data-slot" => "progress-label", class: css(:label))
76
+ end
77
+
78
+ def value_part
79
+ return unless show_value
80
+
81
+ content_tag(:span, percent_text, "data-slot" => "progress-value", class: css(:value))
82
+ end
83
+
84
+ def track
85
+ content_tag(:div, "data-slot" => "progress-track", class: css(:track)) do
86
+ content_tag(:div, nil, "data-slot" => "progress-indicator", class: css(:indicator),
87
+ style: "width: #{percent.round(4)}%")
88
+ end
89
+ end
90
+
91
+ private :percent, :percent_text, :root_attributes
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Progress
6
+ # The Progress preview: mid-flight, complete, and a custom max.
7
+ class Preview < Poetry::Core::Preview::Base
8
+ def default
9
+ render_component(value: 60, label: "Uploading photos")
10
+ end
11
+
12
+ def complete
13
+ render_component(value: 100, label: "Backup")
14
+ end
15
+
16
+ def custom_max_without_value
17
+ render_component(value: 3, max: 8, label: "Steps completed", show_value: false)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Progress
6
+ # Re-expressed through the cn-* theme layer. The root keeps no
7
+ # cn name (its whole poetry surface is structural; upstream's
8
+ # cn-progress-root returns with content in a future theme).
9
+ class Style < Poetry::Core::Style
10
+ base "flex flex-wrap gap-3"
11
+
12
+ element :track, "cn-progress-track relative flex w-full items-center overflow-x-hidden"
13
+ element :indicator, "cn-progress-indicator h-full transition-all"
14
+ element :label, "cn-progress-label"
15
+ element :value, "cn-progress-value"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,80 @@
1
+ <%= form_with(url: url, method: http_method, html: root_attributes.to_attributes) do %>
2
+ <% if progress? %>
3
+ <% custom_progress = progress.to_s %>
4
+ <div <%= tag.attributes({ "class" => css(:progress, class: progress_class), "data-slot" => "questionnaire-progress",
5
+ "role" => "progressbar", "aria-label" => "Questionnaire progress",
6
+ "aria-live" => "polite",
7
+ "data-current" => active_index + 1,
8
+ "data-total" => enabled_items.size,
9
+ "aria-valuemin" => "1",
10
+ "aria-valuemax" => enabled_items.size,
11
+ "aria-valuenow" => active_index + 1,
12
+ "aria-valuetext" => progress_label }
13
+ .merge(custom_progress.present? ? { "data-custom" => "" } : {})
14
+ .merge(stimulus_attributes_for(:progress))) %>>
15
+ <%= custom_progress.presence || progress_label %>
16
+ </div>
17
+ <% end %>
18
+ <% item_models.each do |item| %>
19
+ <%= content_tag(:fieldset, **item_attributes(item)) do %>
20
+ <legend data-slot="questionnaire-title" class="<%= css(:title) %>"><%= item.title %></legend>
21
+ <% if item.description %>
22
+ <p data-slot="questionnaire-description" id="<%= item_dom_id(item, "description") %>"
23
+ class="<%= css(:description) %>"><%= item.description %></p>
24
+ <% end %>
25
+ <div data-slot="questionnaire-choices" class="<%= css(:choices) %>">
26
+ <% item.choices.each_with_index do |choice, index| %>
27
+ <%= content_tag(:label, **choice_attributes(item, choice, index)) do %>
28
+ <input type="<%= input_type(item) %>" name="<%= field_name(item) %>"
29
+ value="<%= choice.value %>" data-slot="questionnaire-choice-input"
30
+ class="<%= css(:choice_input) %>"
31
+ <% if choice.checked %>checked<% end %> <% if choice.disabled || item.disabled %>disabled<% end %>
32
+ <%= tag.attributes({ (choice.checked ? "data-checked" : "data-unchecked") => "" }) %>>
33
+ <span aria-hidden="true" data-slot="questionnaire-choice-indicator" class="<%= css(:choice_indicator) %>">
34
+ <span data-slot="questionnaire-choice-indicator-dot" class="<%= css(:choice_indicator_dot) %>"></span>
35
+ <%= render Poetry::Ui::Icon::Component.new(name: :check, class: css(:choice_indicator_check),
36
+ data: { slot: "questionnaire-choice-indicator-check" }) %>
37
+ </span>
38
+ <span data-slot="questionnaire-choice-label" class="<%= css(:choice_label) %>">
39
+ <span><%= choice.label %></span>
40
+ <% if choice.description %>
41
+ <span data-slot="questionnaire-choice-description" class="<%= css(:choice_description) %>"><%= choice.description %></span>
42
+ <% end %>
43
+ </span>
44
+ <% if (key = shortcut_for(item, index)) %>
45
+ <span aria-hidden="true" data-slot="questionnaire-choice-shortcut" class="<%= css(:choice_shortcut) %>"><%= key %></span>
46
+ <% end %>
47
+ <% end %>
48
+ <% end %>
49
+ <% if item.input %>
50
+ <div data-slot="questionnaire-input-wrapper" class="<%= css(:input_wrapper) %>">
51
+ <input type="text" name="<%= field_name(item) %>" data-slot="questionnaire-input" autocomplete="off"
52
+ class="<%= css(:input) %>" aria-label="<%= item.input.label %>"
53
+ placeholder="<%= item.input.placeholder %>" value="<%= item.input.value %>"
54
+ <%= tag.attributes({ (item.input.value.present? ? "data-filled" : "data-empty") => "" }) %>>
55
+ </div>
56
+ <% end %>
57
+ </div>
58
+ <p data-slot="questionnaire-error" id="<%= item_dom_id(item, "error") %>"
59
+ class="<%= css(:error) %>" hidden><%= item.error_message %></p>
60
+ <% end %>
61
+ <% end %>
62
+ <div data-slot="questionnaire-actions" class="<%= css(:actions) %>">
63
+ <%= render(Poetry::Ui::Button::Component.new(
64
+ variant: :outline, type: :button,
65
+ **nav_button_options(:previous, hidden: enabled_items.size <= 1 || first?)
66
+ )) { previous_label } %>
67
+ <%= render(Poetry::Ui::Button::Component.new(
68
+ variant: :outline, type: :button,
69
+ **nav_button_options(:skip, hidden: active_item.required)
70
+ )) { skip_label } %>
71
+ <%= render(Poetry::Ui::Button::Component.new(
72
+ type: :button,
73
+ **nav_button_options(:next, hidden: enabled_items.size <= 1 || last?)
74
+ )) { next_label } %>
75
+ <%= render(Poetry::Ui::Button::Component.new(
76
+ type: :submit,
77
+ **nav_button_options(:submit, hidden: !last?)
78
+ )) { submit_label } %>
79
+ </div>
80
+ <% end %>