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,396 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # A one-question-at-a-time survey form.
6
+ module Questionnaire
7
+ # A one-question-at-a-time survey. Reach for it when a flow asks
8
+ # several questions in sequence and should submit as one form. The
9
+ # root IS a real form (form_with), items are fieldsets with legend
10
+ # titles, choices are native radio/checkbox inputs, and the free-text
11
+ # answer is a real input - the whole thing serializes as ordinary
12
+ # Rails params with zero JS. The server renders the complete initial
13
+ # state (active item, statuses, shortcut labels, button visibility);
14
+ # the controller owns the runtime transitions.
15
+ #
16
+ # Multiple-selection items name their checkboxes "<name>[]" so
17
+ # params arrive as arrays.
18
+ #
19
+ # @example
20
+ # render Poetry::Ui::Questionnaire::Component.new(url: "/surveys") do |survey|
21
+ # survey.with_item(name: "mood", title: "How was your week?") do |item|
22
+ # item.with_choice(value: "good", label: "Good")
23
+ # item.with_choice(value: "bad", label: "Bad")
24
+ # end
25
+ # end
26
+ class Component < Poetry::Core::Component
27
+ # The closed vocabulary for the shortcuts axis.
28
+ SHORTCUT_MODES = %i[letters numbers].freeze
29
+ # The key labels each shortcut mode assigns, in document order.
30
+ SHORTCUT_KEYS = {
31
+ letters: ("A".."Z").to_a,
32
+ numbers: ("1".."9").to_a
33
+ }.freeze
34
+
35
+ # Projected into the registry, llms.txt, and the agent surface.
36
+ AGENT_RULES = [
37
+ "The root is a REAL form (url:/method:) - answers submit as ordinary params; " \
38
+ "validate server-side and re-render invalid items with error:.",
39
+ "One with_item per question (name: is the param key); choices via item.with_choice, " \
40
+ "an optional free-text answer via item.with_input.",
41
+ "multiple: true renders checkboxes named <name>[] (Rails array params) - " \
42
+ "a recorded divergence from the ported source's repeated bare names.",
43
+ "required: true gates Next/submit client-side; the server stays the truth on submit.",
44
+ "shortcuts: :letters or :numbers labels each choice with a key (server-rendered) " \
45
+ "and enables one-keystroke answering.",
46
+ "Skip renders only while the active item is optional - never force-hide it.",
47
+ "with_progress { custom } replaces the readout; data-current/data-total on the " \
48
+ "progress element and a [data-progress-count] child stay live for segment bars " \
49
+ "and counters."
50
+ ].freeze
51
+
52
+ renders_one :progress, doc: "with_progress (bare) renders the auto \"Question X of Y\" text; with_progress " \
53
+ "{ custom } replaces it (marked data-custom so the controller leaves it alone). " \
54
+ "class: merges onto the progress element (e.g. w-full for a full-width segment " \
55
+ "bar over the base w-fit)."
56
+ alias __vc_with_progress with_progress
57
+
58
+ # Opts the progress readout in. Bare, it renders the live
59
+ # "Question X of Y" text; a block replaces the text; class: merges
60
+ # onto the progress element.
61
+ def with_progress(**options, &)
62
+ @progress_class = options[:class]
63
+ __vc_with_progress(&)
64
+ end
65
+
66
+ # @api private
67
+ attr_reader :progress_class
68
+
69
+ use_stimulus do
70
+ on :root do
71
+ controller :questionnaire do
72
+ register
73
+ value :shortcuts, from: :shortcuts_string, if: -> { shortcuts.present? }
74
+ action :keydown, on: :keydown
75
+ action :submit, on: :submit
76
+ action :reset, on: :reset
77
+ action :change, on: :change
78
+ action :input, on: :input
79
+ end
80
+ end
81
+ on :progress do
82
+ controller(:questionnaire) { target :progress }
83
+ end
84
+ on :previous do
85
+ controller :questionnaire do
86
+ target :previous
87
+ action :previous
88
+ end
89
+ end
90
+ on :skip do
91
+ controller :questionnaire do
92
+ target :skip
93
+ action :skip
94
+ end
95
+ end
96
+ on :next_button do
97
+ controller :questionnaire do
98
+ target :next
99
+ action :next
100
+ end
101
+ end
102
+ on :submit_button do
103
+ controller(:questionnaire) { target :submit }
104
+ end
105
+ end
106
+
107
+ option :url, :string, required: true, doc: "The form's submit URL - answers post here as ordinary params."
108
+ option :http_method, :symbol, default: :post,
109
+ doc: "The form's HTTP verb. Named http_method (not method:) - an option named " \
110
+ "`method` would shadow Object#method."
111
+ option :id, :string, doc: "The root form's DOM id; item element ids derive from it."
112
+ option :shortcuts, :symbol,
113
+ doc: "nil (off), :letters (A, B, C...) or :numbers (1-9): server- rendered key labels + one-keystroke " \
114
+ "answering."
115
+ option :default_item, :string, doc: "The initially active item by name; default is the first item."
116
+ option :previous_label, :string, default: "Previous", doc: "The back-navigation button's text."
117
+ option :skip_label, :string, default: "Skip",
118
+ doc: "The skip button's text (shown only while the active item is optional)."
119
+ option :next_label, :string, default: "Next", doc: "The forward-navigation button's text."
120
+ option :submit_label, :string, default: "Submit",
121
+ doc: "The final submit button's text (replaces Next on the last item)."
122
+
123
+ validates :shortcuts, inclusion: { in: SHORTCUT_MODES }, allow_nil: true
124
+
125
+ part "questionnaire", "The root <form> the controller drives - navigation, validation " \
126
+ "gating, and the keyboard map all ride here"
127
+ part "questionnaire-progress", "The polite progressbar ('Question X of Y'; block " \
128
+ "content replaces the text). Always carries live " \
129
+ "data-current/data-total, and a [data-progress-count] " \
130
+ "child gets the live 'X of Y' text - custom segment " \
131
+ "bars ride data-[current=N] variants",
132
+ states: {
133
+ "data-custom" => "block content supplied - the controller leaves the text alone",
134
+ "data-current" => "always - the active question number (live)",
135
+ "data-total" => "always - the enabled question count (live)"
136
+ }
137
+ part "questionnaire-item", "One question <fieldset> - exactly one is active",
138
+ states: {
139
+ "data-name" => "always - the item's param name",
140
+ "data-active" => "the visible question (inactive items are hidden + inert)",
141
+ "data-status" => { condition: "always - the answer state",
142
+ values: %w[unanswered answered skipped] },
143
+ "data-required" => "required: true - Skip hides and Next validates",
144
+ "data-multiple" => "multiple: true - checkbox choices named <name>[]",
145
+ "data-invalid" => "validation attempted and unanswered - the error shows",
146
+ "data-validated" => "a navigation has demanded this answer at least once",
147
+ "data-skipped" => "explicitly skipped (cleared by any interaction)"
148
+ }
149
+ part "questionnaire-title", "The question heading - a real <legend>"
150
+ part "questionnaire-description", "Muted copy under the title; its id rides the " \
151
+ "fieldset's aria-describedby"
152
+ part "questionnaire-choices", "The answer grid for one item"
153
+ part "questionnaire-choice", "One answer <label> wrapping its native input",
154
+ states: {
155
+ "data-type" => { condition: "always - the input kind",
156
+ values: %w[radio checkbox] },
157
+ "data-checked" => "the choice is selected",
158
+ "data-unchecked" => "the choice is not selected",
159
+ "data-shortcut" => "shortcuts: on - the key label (A, B... or 1-9)",
160
+ "data-disabled" => "choice or item disabled"
161
+ }
162
+ part "questionnaire-choice-input", "THE native radio/checkbox - stretched invisibly " \
163
+ "over the row (paste of the input-otp posture)",
164
+ states: {
165
+ "data-checked" => "selected", "data-unchecked" => "not selected"
166
+ }
167
+ part "questionnaire-choice-indicator", "The box/circle glyph (aria-hidden) - dot for " \
168
+ "radio, check for checkbox"
169
+ part "questionnaire-choice-indicator-dot", "The radio dot (shown while checked)"
170
+ part "questionnaire-choice-indicator-check", "The checkbox check icon (shown while " \
171
+ "checked)"
172
+ part "questionnaire-choice-label", "The label column - answer text over the optional " \
173
+ "description"
174
+ part "questionnaire-choice-description", "Muted copy under the answer text"
175
+ part "questionnaire-choice-shortcut", "The key hint chip (aria-hidden; hidden unless " \
176
+ "the choice carries data-shortcut)"
177
+ part "questionnaire-input-wrapper", "The free-text answer's positioning wrapper"
178
+ part "questionnaire-input", "The free-text answer - a real text input named after " \
179
+ "the item",
180
+ states: {
181
+ "data-filled" => "has a value (counts as answered)",
182
+ "data-empty" => "blank"
183
+ }
184
+ part "questionnaire-error", "The validation message (hidden until a navigation " \
185
+ "demands the answer; role=alert while shown)"
186
+ part "questionnaire-actions", "The navigation row - Previous / Skip / Next / Submit " \
187
+ "(the buttons ride composed Buttons, so those elements " \
188
+ "belong to Button's anatomy, not this contract; each " \
189
+ "carries data-visible/data-hidden + hidden/inert)"
190
+
191
+ # @api private
192
+ def before_render
193
+ # Evaluate the composition block first - with_item is hand-rolled
194
+ # (not a VC slot), so nothing else forces the block this early.
195
+ content
196
+ raise ArgumentError, "Questionnaire needs at least one with_item" if item_models.empty?
197
+ end
198
+
199
+ # Adds one question. name: is the param key, title: the visible
200
+ # heading; the yielded builder takes answers (with_choice) and an
201
+ # optional free-text field (with_input).
202
+ # Hand-rolled (not a VC slot): the yielded builder is a plain
203
+ # object, and ViewComponent lambda slots only forward to component
204
+ # returns - a builder return wraps as nil.
205
+ def with_item(**, &block)
206
+ item = Item.new(**)
207
+ block&.call(item)
208
+ item_models << item
209
+ item
210
+ end
211
+
212
+ # @api private
213
+ def item_models = (@item_models ||= [])
214
+
215
+ # @api private
216
+ def questionnaire_id
217
+ @questionnaire_id ||= if (token = dom_id_token(id))
218
+ "questionnaire-#{token}"
219
+ else
220
+ poetry_instance_id("questionnaire")
221
+ end
222
+ end
223
+
224
+ # @api private
225
+ def enabled_items = item_models.reject(&:disabled)
226
+
227
+ # @api private
228
+ def active_item
229
+ @active_item ||= enabled_items.find { |item| item.name == default_item } ||
230
+ enabled_items.first
231
+ end
232
+
233
+ # @api private
234
+ def active_index = enabled_items.index(active_item) || 0
235
+
236
+ # @api private
237
+ def first? = active_index.zero?
238
+ # @api private
239
+ def last? = active_index >= enabled_items.size - 1
240
+
241
+ # @api private
242
+ def progress_label
243
+ "Question #{active_index + 1} of #{enabled_items.size}"
244
+ end
245
+
246
+ # @api private
247
+ def shortcuts_string = shortcuts.to_s
248
+
249
+ # Server-side shortcut assignment: keys in document order per item;
250
+ # the controller only handles keystrokes.
251
+ # @api private
252
+ def shortcut_for(_item, index)
253
+ return nil unless shortcuts
254
+
255
+ SHORTCUT_KEYS.fetch(shortcuts)[index]
256
+ end
257
+
258
+ # @api private
259
+ def item_dom_id(item, suffix)
260
+ "#{questionnaire_id}-#{item.name.to_s.parameterize}-#{suffix}"
261
+ end
262
+
263
+ # @api private
264
+ def field_name(item) = item.multiple ? "#{item.name}[]" : item.name.to_s
265
+
266
+ # @api private
267
+ def input_type(item) = item.multiple ? "checkbox" : "radio"
268
+
269
+ # @api private
270
+ def root_attributes
271
+ html_attributes.merge_if_not_set(
272
+ { "data-slot" => "questionnaire", "id" => questionnaire_id }
273
+ .merge(stimulus_attributes_for(:root))
274
+ .merge(component_data_attributes)
275
+ )
276
+ end
277
+
278
+ # @api private
279
+ def item_attributes(item)
280
+ active = item == active_item
281
+ attrs = {
282
+ "class" => css(:item, class: item.class_name), "data-slot" => "questionnaire-item",
283
+ "data-name" => item.name, "data-status" => item.status,
284
+ "aria-describedby" => (item.description ? item_dom_id(item, "description") : nil)
285
+ }.compact
286
+ attrs["data-active"] = "" if active
287
+ attrs["hidden"] = "hidden" unless active
288
+ attrs["inert"] = "inert" unless active
289
+ attrs["data-required"] = "" if item.required
290
+ attrs["data-multiple"] = "" if item.multiple
291
+ attrs["disabled"] = "disabled" if item.disabled
292
+ attrs
293
+ end
294
+
295
+ # Symbol-keyed Button kwargs for one nav action: part classes,
296
+ # slot, wiring, and the server-rendered visibility stamp.
297
+ # @api private
298
+ def nav_button_options(kind, hidden:)
299
+ stimulus_element = { next: :next_button, submit: :submit_button }.fetch(kind, kind)
300
+ attrs = { class: css(kind), "data-slot": "questionnaire-#{kind}" }
301
+ attrs.merge!(stimulus_attributes_for(stimulus_element).transform_keys(&:to_sym))
302
+ if hidden
303
+ attrs.merge!(hidden: true, inert: true, tabindex: -1,
304
+ "aria-hidden": "true", "data-hidden": "")
305
+ else
306
+ attrs[:"data-visible"] = ""
307
+ end
308
+ attrs
309
+ end
310
+
311
+ # @api private
312
+ def choice_attributes(item, choice, index)
313
+ attrs = {
314
+ "class" => css(:choice), "data-slot" => "questionnaire-choice",
315
+ "data-type" => input_type(item)
316
+ }
317
+ attrs[choice.checked ? "data-checked" : "data-unchecked"] = ""
318
+ attrs["data-disabled"] = "" if choice.disabled || item.disabled
319
+ if (key = shortcut_for(item, index))
320
+ attrs["data-shortcut"] = key
321
+ end
322
+ attrs
323
+ end
324
+
325
+ # One answer row's data, built via Item#with_choice.
326
+ # @api private
327
+ Choice = Struct.new(:value, :label, :description, :checked, :disabled, keyword_init: true)
328
+ # The free-text answer's data, built via Item#with_input.
329
+ # @api private
330
+ TextInput = Struct.new(:label, :placeholder, :value, keyword_init: true)
331
+
332
+ # One question: title/description as args, choices and the
333
+ # optional free-text input via the yielded builder.
334
+ class Item
335
+ attr_reader :name, :title, :description, :required, :multiple, :disabled,
336
+ :error, :choices, :input, :class_name
337
+
338
+ # @api private
339
+ def initialize(name:, title:, **options)
340
+ @name = name
341
+ @title = title
342
+ @class_name = options[:class]
343
+ @description = options[:description]
344
+ @required = options.fetch(:required, false)
345
+ @multiple = options.fetch(:multiple, false)
346
+ @disabled = options.fetch(:disabled, false)
347
+ @error = options[:error]
348
+ @choices = []
349
+ @input = nil
350
+ end
351
+
352
+ # Adds one answer to the question.
353
+ # @param value [String] the submitted param value
354
+ # @param label [String] the visible answer text
355
+ # @param description [String, nil] muted copy under the answer text
356
+ # @param checked [Boolean] pre-selects the answer
357
+ # @param disabled [Boolean] renders the answer unpickable
358
+ def with_choice(value:, label:, description: nil, checked: false, disabled: false)
359
+ @choices << Choice.new(value: value, label: label, description: description,
360
+ checked: checked, disabled: disabled)
361
+ self
362
+ end
363
+
364
+ # Adds the optional free-text answer field, named after the item.
365
+ # @param label [String] the field's accessible label
366
+ # @param placeholder [String, nil] hint text while empty
367
+ # @param value [String, nil] the pre-filled answer
368
+ def with_input(label:, placeholder: nil, value: nil)
369
+ @input = TextInput.new(label: label, placeholder: placeholder, value: value)
370
+ self
371
+ end
372
+
373
+ # @api private
374
+ def default_error
375
+ if required
376
+ "Choose an answer to continue."
377
+ else
378
+ "Choose an answer or skip this question."
379
+ end
380
+ end
381
+
382
+ # @api private
383
+ def error_message = error || default_error
384
+
385
+ # @api private
386
+ def answered?
387
+ choices.any?(&:checked) || input&.value.to_s.strip != ""
388
+ end
389
+
390
+ # @api private
391
+ def status = answered? ? "answered" : "unanswered"
392
+ end
393
+ end
394
+ end
395
+ end
396
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Questionnaire
6
+ # The Questionnaire preview matrix: the hero three-question flow
7
+ # (required single choice + free text, optional multiple, required
8
+ # closer), plus the per-axis scenarios the contracts hold to DOM -
9
+ # shortcuts, an answered/checked state, a skipped optional item, a
10
+ # disabled choice, and a custom progress readout.
11
+ class Preview < Poetry::Core::Preview::Base
12
+ def default
13
+ render_component(url: "#", shortcuts: :letters, class: "w-full max-w-md") do |q|
14
+ q.with_progress
15
+ q.with_item(name: "direction", title: "What should the agent build next?",
16
+ description: "Choose a direction or describe another task.",
17
+ required: true) do |item|
18
+ item.with_choice(value: "tool-calls", label: "Tool call timeline",
19
+ description: "Show what the agent ran and what came back.")
20
+ item.with_choice(value: "approvals", label: "Approval checkpoints",
21
+ description: "Ask before sensitive or destructive actions.")
22
+ item.with_input(label: "Another agent feature", placeholder: "Describe another feature…")
23
+ end
24
+ q.with_item(name: "signals", title: "What should every update include?",
25
+ description: "Select all that apply, or skip this question.",
26
+ multiple: true) do |item|
27
+ item.with_choice(value: "progress", label: "Progress")
28
+ item.with_choice(value: "decisions", label: "Decisions")
29
+ item.with_choice(value: "risks", label: "Risks")
30
+ end
31
+ q.with_item(name: "timing", title: "When should work begin?", required: true) do |item|
32
+ item.with_choice(value: "now", label: "Start now")
33
+ item.with_choice(value: "backlog", label: "Add it to the backlog")
34
+ end
35
+ end
36
+ end
37
+
38
+ # Server-rendered answered state: checked choice, answered status,
39
+ # the second item active via default_item.
40
+ def answered_and_resumed
41
+ render_component(url: "#", default_item: "detail", class: "w-full max-w-md") do |q|
42
+ q.with_progress
43
+ q.with_item(name: "prototype", title: "What should we prototype?", required: true) do |item|
44
+ item.with_choice(value: "delegation", label: "Delegation", checked: true)
45
+ item.with_choice(value: "prompts", label: "Question prompts")
46
+ end
47
+ q.with_item(name: "detail", title: "How much detail?") do |item|
48
+ item.with_choice(value: "focused", label: "Focused")
49
+ item.with_choice(value: "complete", label: "Complete flow")
50
+ end
51
+ end
52
+ end
53
+
54
+ # Server-rendered CHECKED choices of both kinds on one screen: the
55
+ # checkbox's check glyph and the radio's dot, seated in their boxes -
56
+ # the geometry a golden must hold (an unsized glyph hangs off the box).
57
+ def checked_choices
58
+ render_component(url: "#", shortcuts: :letters, class: "w-full max-w-md") do |q|
59
+ q.with_progress
60
+ q.with_item(name: "signals", title: "What should every update include?",
61
+ description: "Two are already selected.", multiple: true) do |item|
62
+ item.with_choice(value: "progress", label: "Progress", checked: true)
63
+ item.with_choice(value: "decisions", label: "Decisions",
64
+ description: "Which options were weighed and what won.", checked: true)
65
+ item.with_choice(value: "risks", label: "Risks")
66
+ end
67
+ q.with_item(name: "timing", title: "When should work begin?", required: true) do |item|
68
+ item.with_choice(value: "now", label: "Start now", checked: true)
69
+ item.with_choice(value: "backlog", label: "Add it to the backlog")
70
+ end
71
+ end
72
+ end
73
+
74
+ # Number shortcuts + a disabled choice + a pre-filled free answer.
75
+ def numbers_and_disabled
76
+ render_component(url: "#", shortcuts: :numbers, class: "w-full max-w-md") do |q|
77
+ q.with_item(name: "cadence", title: "How often should we check in?",
78
+ description: "Weekly keeps the loop tight.") do |item|
79
+ item.with_choice(value: "daily", label: "Daily", disabled: true)
80
+ item.with_choice(value: "weekly", label: "Weekly")
81
+ item.with_input(label: "Another cadence", placeholder: "Every…", value: "Fortnightly")
82
+ end
83
+ end
84
+ end
85
+
86
+ # A custom progress readout (data-custom - the controller leaves it).
87
+ def custom_progress
88
+ render_component(url: "#", class: "w-full max-w-md") do |q|
89
+ q.with_progress { "Step 1 · Kickoff" }
90
+ q.with_item(name: "kickoff", title: "Ready to start?", required: true) do |item|
91
+ item.with_choice(value: "yes", label: "Yes")
92
+ item.with_choice(value: "later", label: "Later")
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Questionnaire
6
+ # Style dictionary for the Questionnaire family: structural classes
7
+ # inline; the visual voice lives in each theme's cn-questionnaire-*
8
+ # rules.
9
+ class Style < Poetry::Core::Style
10
+ base "cn-questionnaire flex w-full min-w-0 flex-col"
11
+
12
+ element :progress, "cn-questionnaire-progress min-h-[1lh] w-fit min-w-[14ch] font-medium " \
13
+ "text-muted-foreground tabular-nums"
14
+ element :item, "cn-questionnaire-item min-w-0 border-0 p-0 outline-none"
15
+ element :title, "cn-questionnaire-title cn-font-heading text-pretty"
16
+ element :description, "cn-questionnaire-description text-pretty text-muted-foreground"
17
+ element :choices, "cn-questionnaire-choices group/questionnaire-choices grid min-w-0"
18
+ element :choice, "cn-questionnaire-choice group/questionnaire-choice relative flex " \
19
+ "min-h-11 cursor-pointer items-start text-start transition-colors " \
20
+ "outline-none select-none data-disabled:pointer-events-none " \
21
+ "data-disabled:cursor-not-allowed data-disabled:opacity-50"
22
+ element :choice_input, "absolute inset-0 z-10 size-full cursor-pointer opacity-0"
23
+ element :choice_indicator, "cn-questionnaire-choice-indicator pointer-events-none " \
24
+ "relative flex shrink-0 items-center justify-center border " \
25
+ "group-data-[type=radio]/questionnaire-choice:rounded-full"
26
+ element :choice_indicator_dot, "cn-questionnaire-choice-indicator-dot hidden rounded-full " \
27
+ "group-data-[type=checkbox]/questionnaire-choice:hidden " \
28
+ "group-data-checked/questionnaire-choice:block"
29
+ element :choice_indicator_check, "cn-questionnaire-choice-indicator-check hidden " \
30
+ "group-data-[type=radio]/questionnaire-choice:hidden " \
31
+ "group-data-checked/questionnaire-choice:block"
32
+ element :choice_label, "cn-questionnaire-choice-content flex min-w-0 flex-1 flex-col leading-snug"
33
+ element :choice_description, "cn-questionnaire-choice-description"
34
+ element :choice_shortcut, "cn-questionnaire-shortcut pointer-events-none ms-auto hidden shrink-0 " \
35
+ "group-data-[shortcut]/questionnaire-choice:inline-flex"
36
+ element :input_wrapper, "cn-questionnaire-input-wrapper group/questionnaire-input " \
37
+ "relative min-w-0"
38
+ element :input, "cn-questionnaire-input min-h-11 w-full min-w-0 " \
39
+ "transition-[color,box-shadow,background-color] outline-none " \
40
+ "disabled:pointer-events-none disabled:cursor-not-allowed " \
41
+ "disabled:opacity-50 sm:min-h-0 selection:bg-primary " \
42
+ "selection:text-primary-foreground placeholder:text-muted-foreground"
43
+ element :error, "cn-questionnaire-error text-destructive"
44
+ element :actions, "cn-questionnaire-actions grid min-h-11 w-full " \
45
+ "grid-cols-[minmax(0,1fr)_auto_auto] items-center"
46
+ element :previous, "col-start-1 row-start-1 min-h-11 justify-self-start sm:min-h-0"
47
+ element :skip, "col-start-2 row-start-1 min-h-11 justify-self-end sm:min-h-0"
48
+ element :next, "col-start-3 row-start-1 min-h-11 justify-self-end sm:min-h-0"
49
+ element :submit, "col-start-3 row-start-1 min-h-11 justify-self-end sm:min-h-0"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,4 @@
1
+ <%# Items render as direct children of the radiogroup root (grid gap-3) -
2
+ the roving collection queries [data-poetry-collection-item] in DOM
3
+ order; the label-pairing rows are transparent to it. %>
4
+ <%= content_tag(:div, safe_join(items.map(&:to_s)), **root_attributes.to_attributes) %>