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,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # The FieldGroup family - the stacking container for Fields.
6
+ module FieldGroup
7
+ # The FieldGroup - the Field family's stacking container: fields,
8
+ # fieldsets, and separators stack with the
9
+ # theme's rhythm instead of hand-spaced flex columns. It is also the
10
+ # CSS `@container` scope responsive fields key on - Field's
11
+ # orientation: :responsive flips to a row only once ITS field-group
12
+ # container passes the md mark.
13
+ #
14
+ # @example Stacking fields with the theme's rhythm
15
+ # render Poetry::Ui::FieldGroup::Component.new do
16
+ # safe_join([
17
+ # render(Poetry::Ui::Field::Component.new) { ... },
18
+ # render(Poetry::Ui::FieldSeparator::Component.new),
19
+ # render(Poetry::Ui::Field::Component.new) { ... }
20
+ # ])
21
+ # end
22
+ class Component < Poetry::Core::Component
23
+ # The closed vocabulary for the variant axis.
24
+ VARIANTS = %i[default choices].freeze
25
+
26
+ # Projected into the registry, llms.txt, and the agent surface.
27
+ AGENT_RULES = [
28
+ "Stack Fields (and Fieldsets) with poetry_field_group - the theme owns the rhythm; " \
29
+ "never hand-space a form column with gap utilities.",
30
+ "variant: :choices packs a run of horizontal checkbox/switch fields tighter (the " \
31
+ "choice-group form).",
32
+ "Field's orientation: :responsive is container-driven: it needs a FieldGroup " \
33
+ "ancestor to measure against - without one it stays stacked."
34
+ ].freeze
35
+
36
+ style :variant, default: :default, required: true, variants: VARIANTS,
37
+ doc: ":choices packs a run of horizontal checkbox/switch fields tighter."
38
+
39
+ part "field-group", "The stacking container - fields, fieldsets, and separators " \
40
+ "render as direct children; also the @container responsive " \
41
+ "fields measure against"
42
+
43
+ # Renders the stacking container around the content.
44
+ # @api private
45
+ def call
46
+ content_tag(:div, content, **root_attributes.to_attributes)
47
+ end
48
+
49
+ # The container's attributes.
50
+ # @api private
51
+ def root_attributes
52
+ html_attributes.merge_if_not_set(
53
+ { "data-slot" => "field-group" }.merge(component_data_attributes)
54
+ )
55
+ end
56
+
57
+ private :root_attributes
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,30 @@
1
+ <%# Shared template for the FieldGroup previews: default stacks wired
2
+ quartet fields around a separator; choices: packs horizontal
3
+ checkbox rows (the tighter run the variant exists for). %>
4
+ <div class="w-96 p-4">
5
+ <%= render component do %>
6
+ <% if local_assigns[:choices] %>
7
+ <%= render Poetry::Ui::Field::Component.new(id: "field-group-preview-disks", label_text: "Hard disks",
8
+ orientation: :horizontal) do |field| %>
9
+ <%= render Poetry::Ui::Checkbox::Component.new(name: "disks", checked: true,
10
+ **field.control_attributes.transform_keys(&:to_sym)) %>
11
+ <% end %>
12
+ <%= render Poetry::Ui::Field::Component.new(id: "field-group-preview-external", label_text: "External disks",
13
+ orientation: :horizontal) do |field| %>
14
+ <%= render Poetry::Ui::Checkbox::Component.new(name: "external",
15
+ **field.control_attributes.transform_keys(&:to_sym)) %>
16
+ <% end %>
17
+ <% else %>
18
+ <%= render Poetry::Ui::Field::Component.new(id: "field-group-preview-email", label_text: "Email") do |field| %>
19
+ <%= render Poetry::Ui::Input::Component.new(type: "email", name: "email",
20
+ **field.control_attributes.transform_keys(&:to_sym)) %>
21
+ <% end %>
22
+ <%= render Poetry::Ui::FieldSeparator::Component.new.with_content("Or continue with") %>
23
+ <%= render Poetry::Ui::Field::Component.new(id: "field-group-preview-sso", label_text: "SSO domain",
24
+ hint: "Your identity provider's domain.") do |field| %>
25
+ <%= render Poetry::Ui::Input::Component.new(name: "sso",
26
+ **field.control_attributes.transform_keys(&:to_sym)) %>
27
+ <% end %>
28
+ <% end %>
29
+ <% end %>
30
+ </div>
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module FieldGroup
6
+ # The stacking container over real wired Fields - rendering rides
7
+ # the sidecar preview.html.erb (the Field preview precedent).
8
+ class Preview < Poetry::Core::Preview::Base
9
+ def default
10
+ render_with(component: Component.new)
11
+ end
12
+
13
+ # The tighter checkbox-run rhythm (upstream's checkbox-group form).
14
+ def choices
15
+ render_with(component: Component.new(variant: :choices), choices: true)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module FieldGroup
6
+ # Upstream FieldGroup structure: the named group/@container pair is
7
+ # what responsive fields and theme rhythm rules key on. The stack
8
+ # gap is design and rides the theme under cn-field-group; :choices
9
+ # emits its marker for the tighter checkbox-run rhythm (upstream
10
+ # overrides data-slot to checkbox-group for the same effect - poetry
11
+ # keeps the slot stable and mirrors the variant as a class, the
12
+ # drawer-direction marker pattern).
13
+ class Style < Poetry::Core::Style
14
+ base "cn-field-group group/field-group @container/field-group flex w-full flex-col"
15
+
16
+ # Default is the base state and emits nothing (the marker/core-X
17
+ # empty-variant precedent).
18
+ variant :variant, {
19
+ default: "",
20
+ choices: "cn-field-group-choices"
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # The FieldSeparator family - the divider row between stacked fields.
6
+ module FieldSeparator
7
+ # The FieldSeparator - the divider between stacked fields inside a
8
+ # FieldGroup: a Separator drawn across the
9
+ # row, with an optional inline caption riding on top ("Or continue
10
+ # with"). The caption is visual chrome on a decorative rule - the
11
+ # Separator inside stays aria-hidden either way.
12
+ #
13
+ # @example A captioned divider between stacked fields
14
+ # render Poetry::Ui::FieldSeparator::Component.new { "Or continue with" }
15
+ class Component < Poetry::Core::Component
16
+ # Projected into the registry, llms.txt, and the agent surface.
17
+ AGENT_RULES = [
18
+ "Divides stacked fields inside a poetry_field_group - not a general-purpose rule " \
19
+ "(that is poetry_separator).",
20
+ "Pass a block for the inline caption form (\"Or continue with\") - the caption sits " \
21
+ "on the line, backed by the page background."
22
+ ].freeze
23
+
24
+ part "field-separator", "The divider row - a decorative Separator drawn across it",
25
+ states: {
26
+ "data-content" => { condition: "always - whether the inline caption renders",
27
+ values: %w[true false] }
28
+ }
29
+ part "field-separator-content", "The inline caption span (block content) - sits on " \
30
+ "the line, backed by the page background"
31
+
32
+ # Renders the divider row (rule + optional caption).
33
+ # @api private
34
+ def call
35
+ content_tag(:div, **root_attributes.to_attributes) do
36
+ safe_join([rule, caption].compact)
37
+ end
38
+ end
39
+
40
+ # The divider row's attributes.
41
+ # @api private
42
+ def root_attributes
43
+ html_attributes.merge_if_not_set(
44
+ # Normalized to "true"/"false": ViewComponent's content? is
45
+ # truthy/falsy, not boolean (defined?-strings, blocks), Rails
46
+ # drops false attribute values, and the declared-state
47
+ # contract wants the pair always visible.
48
+ { "data-slot" => "field-separator", "data-content" => (content? ? "true" : "false") }
49
+ .merge(component_data_attributes)
50
+ )
51
+ end
52
+
53
+ private
54
+
55
+ def rule
56
+ render(Separator::Component.new(class: css(:line)))
57
+ end
58
+
59
+ def caption
60
+ return unless content?
61
+
62
+ content_tag(:span, content, "data-slot" => "field-separator-content",
63
+ "class" => css(:content))
64
+ end
65
+
66
+ private :root_attributes
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module FieldSeparator
6
+ class Preview < Poetry::Core::Preview::Base
7
+ def default
8
+ render_component
9
+ end
10
+
11
+ # The inline caption form - the line breaks around the text.
12
+ def with_caption
13
+ render_component { "Or continue with" }
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module FieldSeparator
6
+ # Upstream FieldSeparator structure: the row is a positioning context
7
+ # for the absolute Separator; the caption floats centered on it,
8
+ # backed by the page background so the line breaks around the text.
9
+ # Height/margins/type ride the theme under cn-field-separator.
10
+ class Style < Poetry::Core::Style
11
+ base "cn-field-separator relative"
12
+
13
+ element :content, "cn-field-separator-content relative mx-auto block w-fit bg-background"
14
+ # The rule the content sits on (dictionary-resident for the
15
+ # safelist harvest).
16
+ element :line, "absolute inset-0 top-1/2"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # The Fieldset family - a named group of related fields.
6
+ module Fieldset
7
+ # The Fieldset - the Field family's GROUP layer: a run of related
8
+ # fields inside a
9
+ # real <fieldset>, named by a real <legend>. The native pair carries
10
+ # the group semantics assistive technology already understands - no
11
+ # aria wiring to
12
+ # hand-write, which is why legend: is required rather than optional
13
+ # chrome. legend_variant: :label renders the legend at label size
14
+ # while the group keeps its accessible name.
15
+ #
16
+ # @example A named group of address fields
17
+ # render Poetry::Ui::Fieldset::Component.new(legend: "Shipping address") do
18
+ # # poetry_field_group with the fields
19
+ # end
20
+ class Component < Poetry::Core::Component
21
+ # The closed vocabulary for the legend_variant axis.
22
+ LEGEND_VARIANTS = %i[legend label].freeze
23
+
24
+ # Projected into the registry, llms.txt, and the agent surface.
25
+ AGENT_RULES = [
26
+ "A run of related fields gets poetry_fieldset with legend: - the group's accessible " \
27
+ "name (a bare <div> around fields tells AT nothing).",
28
+ "legend_variant: :label renders the legend at label size - use it when the group is " \
29
+ "one setting explained by its rows (checkbox/switch runs).",
30
+ "hint: is the muted description under the legend; per-field hints stay on the fields.",
31
+ "Stack the fields inside with poetry_field_group - never hand-spaced flex columns."
32
+ ].freeze
33
+
34
+ option :legend, :string, required: true, doc: "The group's accessible name - renders as the real <legend>."
35
+ option :legend_variant, :symbol, default: :legend,
36
+ doc: ":label renders the legend at label size - for a group that is one " \
37
+ "setting explained by its rows (checkbox/switch runs)."
38
+ option :hint, :string, doc: "Muted description under the legend; per-field hints stay on the fields."
39
+
40
+ validates :legend_variant, inclusion: { in: LEGEND_VARIANTS }
41
+
42
+ part "field-set", "The <fieldset> root - legend, optional hint, then the fields"
43
+ part "field-legend", "The <legend> - the group's accessible name",
44
+ states: {
45
+ "data-variant" => { condition: "always - the legend's size treatment",
46
+ values: LEGEND_VARIANTS.map(&:to_s) }
47
+ }
48
+ part "field-set-hint", "Muted description under the legend (hint:)"
49
+
50
+ # Enforces the required legend before render.
51
+ # @api private
52
+ def before_render
53
+ raise ArgumentError, "Fieldset requires legend: (the group's accessible name)" if legend.blank?
54
+ end
55
+
56
+ # Renders the <fieldset> (legend, optional hint, then the fields).
57
+ # @api private
58
+ def call
59
+ content_tag(:fieldset, **root_attributes.to_attributes) do
60
+ safe_join([legend_tag, hint_tag, content].compact)
61
+ end
62
+ end
63
+
64
+ # The <fieldset> root's attributes.
65
+ # @api private
66
+ def root_attributes
67
+ html_attributes.merge_if_not_set(
68
+ { "data-slot" => "field-set" }.merge(component_data_attributes)
69
+ )
70
+ end
71
+
72
+ private
73
+
74
+ def legend_tag
75
+ content_tag(:legend, legend, "data-slot" => "field-legend",
76
+ "data-variant" => legend_variant,
77
+ "class" => css(:legend))
78
+ end
79
+
80
+ def hint_tag
81
+ return if hint.blank?
82
+
83
+ content_tag(:p, hint, "data-slot" => "field-set-hint", "class" => css(:hint))
84
+ end
85
+
86
+ private :root_attributes
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,22 @@
1
+ <%# Shared template for the Fieldset previews: the legend/hint pair over a
2
+ FieldGroup of real wired Fields (the canonical composition). %>
3
+ <div class="w-96 p-4">
4
+ <%= render component do %>
5
+ <%= render Poetry::Ui::FieldGroup::Component.new do %>
6
+ <%= render Poetry::Ui::Field::Component.new(id: "fieldset-preview-street", label_text: "Street address") do |field| %>
7
+ <%= render Poetry::Ui::Input::Component.new(name: "street", placeholder: "123 Main St",
8
+ **field.control_attributes.transform_keys(&:to_sym)) %>
9
+ <% end %>
10
+ <div class="grid grid-cols-2 gap-4">
11
+ <%= render Poetry::Ui::Field::Component.new(id: "fieldset-preview-city", label_text: "City") do |field| %>
12
+ <%= render Poetry::Ui::Input::Component.new(name: "city", placeholder: "New York",
13
+ **field.control_attributes.transform_keys(&:to_sym)) %>
14
+ <% end %>
15
+ <%= render Poetry::Ui::Field::Component.new(id: "fieldset-preview-zip", label_text: "Postal code") do |field| %>
16
+ <%= render Poetry::Ui::Input::Component.new(name: "zip", placeholder: "90502",
17
+ **field.control_attributes.transform_keys(&:to_sym)) %>
18
+ <% end %>
19
+ </div>
20
+ <% end %>
21
+ <% end %>
22
+ </div>
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Fieldset
6
+ # The group layer over real nested Fields - rendering rides the
7
+ # sidecar preview.html.erb (nesting controls inside fields needs a
8
+ # real view context, the Field preview precedent).
9
+ class Preview < Poetry::Core::Preview::Base
10
+ def default
11
+ render_with(component: Component.new(legend: "Address information",
12
+ hint: "We need your address to deliver the order."))
13
+ end
14
+
15
+ # The label-sized legend: the group is one setting explained by
16
+ # its rows (the checkbox/switch-run form).
17
+ def label_legend
18
+ render_with(component: Component.new(legend: "Show these items",
19
+ legend_variant: :label,
20
+ hint: "Pick what lands on the desktop."))
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Fieldset
6
+ # Upstream FieldSet/FieldLegend/FieldDescription structure; the
7
+ # group rhythm (gaps, legend margins, type scale) rides the theme.
8
+ # :hint wears cn-field-description - the same style hook as Field's
9
+ # hint, so one theme rule styles description text across the family.
10
+ class Style < Poetry::Core::Style
11
+ base "cn-field-set flex flex-col"
12
+
13
+ element :legend, "cn-field-legend"
14
+ element :hint, "cn-field-description"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ <% if variant == :input %>
2
+ <%= render input_variant_component %>
3
+ <% else %>
4
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
5
+ <%= content_tag(:label, dropzone_attributes) do %>
6
+ <%= tag(:input, control_attributes) %>
7
+ <%= render Poetry::Ui::Icon::Component.new(name: :upload, class: css(:icon)) %>
8
+ <span data-slot="file-input-prompt" class="<%= css(:prompt) %>"><%= prompt_text %></span>
9
+ <% if hint.present? %><span data-slot="file-input-hint" class="<%= css(:hint) %>"><%= hint %></span><% end %>
10
+ <% end %>
11
+ <%= content_tag(:ul, "", list_attributes) %>
12
+ <%= content_tag(:button, t("poetry.file_input.clear"), clear_attributes) %>
13
+ <% end %>
14
+ <% end %>
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # The FileInput family - file selection as a plain control or a dropzone.
6
+ module FileInput
7
+ # The FileInput - file
8
+ # selection in two shapes. variant: :input is the plain styled native
9
+ # control (the Input component with type=file - zero JS, the browser
10
+ # shows the selection). variant: :dropzone is the drag-and-drop
11
+ # surface: a <label> wrapping a visually-hidden native input - so
12
+ # click-to-browse and keyboard access are the PLATFORM's - with JS
13
+ # adding only what HTML cannot
14
+ # (drop, the selection list, clear). The native input is always the
15
+ # form value; ActiveStorage direct upload rides it untouched
16
+ # (data: { direct_upload_url: ... } passes straight through).
17
+ #
18
+ # @example Drag-and-drop upload surface
19
+ # render Poetry::Ui::FileInput::Component.new(
20
+ # variant: :dropzone, name: "attachments[]", multiple: true,
21
+ # hint: "PDF or PNG, up to 10 MB"
22
+ # )
23
+ class Component < Poetry::Core::Component
24
+ # Projected into the registry, llms.txt, and the agent surface.
25
+ AGENT_RULES = [
26
+ "File selection is a FileInput: variant: :input for compact forms, :dropzone when " \
27
+ "dragging is expected (uploads as the page's point) - never a hand-rolled drop div.",
28
+ "The native input is the form value: set name: (multiple: true wants a name ending " \
29
+ "in [] for Rails params); ActiveStorage direct upload attaches to it as usual.",
30
+ "The dropzone's selected-file list and clear button are controller-rendered - compose " \
31
+ "prompt:/hint: copy instead of adding your own list markup.",
32
+ "In a Field, prefer form.file_input (the builder wires id/label/errors); the bare " \
33
+ "component suits standalone dropzones."
34
+ ].freeze
35
+
36
+ use_stimulus do
37
+ on :root do
38
+ controller :file_input do
39
+ register
40
+ value :multiple
41
+ end
42
+ end
43
+ on :dropzone do
44
+ controller :file_input do
45
+ action :dragenter, on: :dragenter
46
+ action :dragover, on: :dragover
47
+ action :dragleave, on: :dragleave
48
+ action :drop, on: :drop
49
+ end
50
+ end
51
+ on :control do
52
+ controller :file_input do
53
+ target :input
54
+ action :changed, on: :change
55
+ end
56
+ end
57
+ on :list do
58
+ controller(:file_input) { target :list }
59
+ end
60
+ on :clear do
61
+ controller :file_input do
62
+ target :clear
63
+ action :clear, on: :click
64
+ end
65
+ end
66
+ end
67
+
68
+ style :variant, default: :input, variants: %i[input dropzone],
69
+ doc: ":input is the compact native control; :dropzone the drag-and-drop surface."
70
+
71
+ option :name, :string,
72
+ doc: "The native input's name - the submitted param (multiple: true wants a name ending in [] for " \
73
+ "Rails params)."
74
+ option :id, :string, doc: "The native input's id (Field/FormBuilder wire it to the label)."
75
+ option :multiple, :boolean, default: false,
76
+ doc: "Allows selecting several files; forwarded to the native input."
77
+ option :accept, :string, doc: "The native accept filter (e.g. \"image/*,.pdf\")."
78
+ option :disabled, :boolean, default: false, doc: "Disables the native input and dims the dropzone."
79
+ option :invalid, :boolean, default: false,
80
+ doc: "Marks the control aria-invalid (set by Field/FormBuilder from model errors)."
81
+ option :described_by, :string, doc: "Ids for the native input's aria-describedby (Field wires this)."
82
+ option :prompt, :string, doc: "The dropzone's instruction line - overrides the translated default."
83
+ option :hint, :string, doc: "Muted constraints copy under the prompt (formats, size limits)."
84
+
85
+ part "file-input", "The dropzone root - wraps the zone, the selection list, and clear",
86
+ states: {
87
+ "data-variant" => { condition: "always on the dropzone root (the input variant " \
88
+ "renders the Input component instead)",
89
+ values: %w[dropzone] },
90
+ "data-dragging" => { condition: "a file drag is over the zone (controller-written, " \
91
+ "enter/leave counted)" },
92
+ "data-populated" => { condition: "the native input holds at least one file " \
93
+ "(controller-written)" }
94
+ }
95
+ part "file-input-dropzone", "The <label> drop surface - dashed, platform click-to-browse; " \
96
+ "its text is the control's accessible name"
97
+ part "file-input-control", "The native <input type=file> - visually hidden in the " \
98
+ "dropzone, THE form value in both variants"
99
+ part "file-input-prompt", "The zone's instruction line (prompt: overrides the default)"
100
+ part "file-input-hint", "Muted constraints copy under the prompt (hint: - formats, size)"
101
+ part "file-input-list", "The selected-file <ul> the controller fills (name + size per " \
102
+ "item; items are controller-built, not server parts)"
103
+ part "file-input-clear", "The clear affordance - hidden until populated; never re-opens " \
104
+ "the picker"
105
+
106
+ # Enforces visible prompt text on the dropzone variant.
107
+ # @api private
108
+ def before_render
109
+ # The dropzone announces itself through the label text; the input
110
+ # variant is named externally (Field/Label) like any Input.
111
+ return unless variant == :dropzone && prompt_text.blank?
112
+
113
+ raise ArgumentError, "FileInput dropzone requires visible prompt text"
114
+ end
115
+
116
+ # The resolved instruction line (prompt: or the translated default).
117
+ # @api private
118
+ def prompt_text
119
+ prompt.presence || t("poetry.file_input.prompt")
120
+ end
121
+
122
+ # The input variant renders the Input component AS the whole
123
+ # surface, so the FileInput's own extra attributes (aria-label,
124
+ # class, data-*) must forward onto it - dropping them would strand
125
+ # the accessible name.
126
+ # @api private
127
+ def input_variant_component
128
+ Input::Component.new(
129
+ type: "file", name: name, disabled: disabled, invalid: invalid,
130
+ **{ "id" => id, "accept" => accept, "multiple" => multiple || nil,
131
+ "aria-describedby" => described_by }.compact,
132
+ **html_attributes.to_attributes.transform_keys(&:to_sym)
133
+ )
134
+ end
135
+
136
+ # The hidden native input's attributes (dropzone variant).
137
+ # @api private
138
+ def control_attributes
139
+ attrs = {
140
+ "type" => "file", "class" => "sr-only", "data-slot" => "file-input-control",
141
+ "name" => name, "id" => id, "accept" => accept,
142
+ "aria-describedby" => described_by
143
+ }.compact
144
+ attrs["multiple"] = true if multiple
145
+ attrs["disabled"] = true if disabled
146
+ attrs["aria-invalid"] = "true" if invalid
147
+ attrs.merge(stimulus_attributes_for(:control))
148
+ end
149
+
150
+ # The dropzone root's attributes.
151
+ # @api private
152
+ def root_attributes
153
+ html_attributes.merge_if_not_set(
154
+ { "data-slot" => "file-input", "data-variant" => variant }
155
+ .merge(component_data_attributes).merge(stimulus_attributes_for(:root))
156
+ )
157
+ end
158
+
159
+ # The <label> drop surface's attributes.
160
+ # @api private
161
+ def dropzone_attributes
162
+ {
163
+ "data-slot" => "file-input-dropzone",
164
+ "class" => "#{css(:dropzone)}#{" #{css(:dropzone_disabled)}" if disabled}"
165
+ }.merge(stimulus_attributes_for(:dropzone))
166
+ end
167
+
168
+ # The controller-filled selection list's attributes.
169
+ # @api private
170
+ def list_attributes
171
+ { "data-slot" => "file-input-list", "class" => css(:list) }
172
+ .merge(stimulus_attributes_for(:list))
173
+ end
174
+
175
+ # The clear button's attributes (hidden until files are selected).
176
+ # @api private
177
+ def clear_attributes
178
+ { "type" => "button", "data-slot" => "file-input-clear", "hidden" => true,
179
+ "class" => css(:clear) }
180
+ .merge(stimulus_attributes_for(:clear))
181
+ end
182
+
183
+ private :prompt_text, :input_variant_component, :control_attributes, :root_attributes, :dropzone_attributes
184
+ private :list_attributes, :clear_attributes
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module FileInput
6
+ class Preview < Poetry::Core::Preview::Base
7
+ # The compact form control: the Input component with type=file -
8
+ # the browser renders the selection, no JS.
9
+ def default
10
+ render_component(name: "document", "aria-label": "Document")
11
+ end
12
+
13
+ def dropzone
14
+ render_component(variant: :dropzone, name: "attachments[]", multiple: true,
15
+ hint: "PNG, JPG or PDF, up to 10 MB each")
16
+ end
17
+
18
+ def dropzone_custom_prompt
19
+ render_component(variant: :dropzone, name: "avatar", accept: "image/*",
20
+ prompt: "Drop your avatar here", hint: "Square images look best")
21
+ end
22
+
23
+ def dropzone_disabled
24
+ render_component(variant: :dropzone, name: "evidence", disabled: true,
25
+ hint: "Uploads are locked while the case is closed")
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end