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,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module DropdownMenu
6
+ # Re-expressed through the cn-* theme layer: panel/item chrome,
7
+ # focus treatments, and the animate chains ride themes/default.css;
8
+ # popper vars, overflow, and the poetry indicator-visibility switch
9
+ # stay inline. Icon glyph classes stay inline per upstream (TSX
10
+ # inlines them on the lucide elements).
11
+ class Style < Poetry::Core::Style
12
+ element :content, "cn-dropdown-menu-content cn-menu-translucent z-50 max-h-(--available-height) " \
13
+ "origin-(--transform-origin) overflow-x-hidden overflow-y-auto " \
14
+ "data-closed:overflow-hidden outline-none w-(--anchor-width)"
15
+
16
+ # group/dropdown-menu-item: bare marker, no CSS - the vega
17
+ # shortcut re-color (group-focus/dropdown-menu-item) keys on it.
18
+ element :item, "cn-dropdown-menu-item group/dropdown-menu-item relative flex cursor-default " \
19
+ "items-center outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0 " \
20
+ "data-disabled:opacity-50 data-disabled:pointer-events-none"
21
+
22
+ element :checkbox_item, "cn-dropdown-menu-checkbox-item relative flex cursor-default items-center " \
23
+ "outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0 " \
24
+ "data-disabled:opacity-50 data-disabled:pointer-events-none"
25
+
26
+ element :radio_item, "cn-dropdown-menu-radio-item relative flex cursor-default items-center outline-hidden " \
27
+ "select-none [&_svg]:pointer-events-none [&_svg]:shrink-0 data-disabled:opacity-50 " \
28
+ "data-disabled:pointer-events-none"
29
+
30
+ element :label, "cn-dropdown-menu-label"
31
+
32
+ element :separator, "cn-dropdown-menu-separator"
33
+
34
+ element :shortcut, "cn-dropdown-menu-shortcut"
35
+
36
+ element :sub_trigger, "cn-dropdown-menu-sub-trigger flex cursor-default items-center outline-hidden " \
37
+ "select-none [&_svg]:pointer-events-none [&_svg]:shrink-0 data-popup-open:bg-accent " \
38
+ "data-popup-open:text-accent-foreground"
39
+
40
+ element :sub_content, "cn-dropdown-menu-sub-content z-50 origin-(--transform-origin) overflow-hidden w-auto"
41
+
42
+ # The indicator wrapper span, named dropdown-menu-item-indicator
43
+ # (the self-identification rule).
44
+ element :item_indicator, "cn-dropdown-menu-item-indicator pointer-events-none absolute " \
45
+ "flex items-center justify-center"
46
+
47
+ # The indicator stays in the DOM (never unmounted while
48
+ # unchecked); the parent item's data-checked/data-unchecked pair
49
+ # drives visibility, so the controller's aria-checked/data-checked
50
+ # flip is the whole toggle.
51
+ element :item_indicator_state, "[[data-unchecked]>&]:hidden"
52
+
53
+ # The source's inline lucide icon classes, named per part.
54
+ element :indicator_check, "size-4"
55
+ element :indicator_circle, "size-2 fill-current"
56
+ element :sub_indicator, "ml-auto size-4"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,10 @@
1
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
2
+ <% if header? %>
3
+ <div data-slot="empty-header" class="<%= css(:header) %>">
4
+ <% if media? %><div data-slot="empty-icon" data-variant="<%= media_variant %>" class="<%= media_classes %>"><%= media %></div><% end %>
5
+ <% if title? %><%= content_tag(title_tag, title, "data-slot" => "empty-title", class: css(:title)) %><% end %>
6
+ <% if description? %><div data-slot="empty-description" class="<%= css(:description) %>"><%= description %></div><% end %>
7
+ </div>
8
+ <% end %>
9
+ <% if content.present? %><div data-slot="empty-content" class="<%= css(:content) %>"><%= content %></div><% end %>
10
+ <% end %>
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # The Empty family - the empty-state block for collections with nothing in them.
6
+ module Empty
7
+ # The Empty state - what a collection shows when there is nothing in
8
+ # it: an optional media glyph, a title, a description, and the actions
9
+ # that fix the emptiness (the content block). Slots compose the header;
10
+ # the content block becomes empty-content.
11
+ #
12
+ # @example Empty collection with a next action
13
+ # render Poetry::Ui::Empty::Component.new do |empty|
14
+ # empty.with_title { "No projects yet" }
15
+ # empty.with_description { "Create your first project to get started." }
16
+ # poetry_button { "New project" }
17
+ # end
18
+ class Component < Poetry::Core::Component
19
+ # Projected into the registry, llms.txt, and the agent surface.
20
+ AGENT_RULES = [
21
+ "An empty collection gets an Empty state with a next action - never a bare 'No results' div.",
22
+ "Compose with the slots (media/title/description); the actions are the content block.",
23
+ "The title renders as a real heading (h3 default) - set title_tag: to fit the page outline.",
24
+ "media_variant: :icon gives the rounded muted icon tile; wrap a poetry_icon in with_media."
25
+ ].freeze
26
+
27
+ renders_one :media, doc: "Optional leading visual above the title - an icon or illustration."
28
+ renders_one :title, doc: "The headline, rendered as a real heading (see title_tag)."
29
+ renders_one :description, doc: "Muted copy under the title."
30
+
31
+ option :title_tag, :symbol, default: :h3,
32
+ doc: "The title's heading level - a real heading element, so set it to fit the " \
33
+ "page outline."
34
+ option :media_variant, :symbol, default: :default,
35
+ doc: "The media slot's treatment; :icon gives the rounded muted icon tile."
36
+
37
+ validates :title_tag, inclusion: { in: %i[h1 h2 h3 h4 h5 h6] }
38
+ validates :media_variant, inclusion: { in: %i[default icon] }
39
+
40
+ part "empty", "The empty-state root - the centered header and content stack"
41
+ part "empty-header", "Wrapper around media/title/description - renders when at " \
42
+ "least one of those slots is set"
43
+ part "empty-icon", "The media slot's box - media_variant: :icon gives the rounded " \
44
+ "muted icon tile",
45
+ states: {
46
+ "data-variant" => { condition: "always - the resolved media_variant",
47
+ values: %w[default icon] }
48
+ }
49
+ part "empty-title", "The title as a real heading (title_tag, h3 by default)"
50
+ part "empty-description", "Muted copy under the title"
51
+ part "empty-content", "The actions that fix the emptiness - the content block " \
52
+ "renders here"
53
+
54
+ # Whether the header wrapper renders (any of media/title/description set).
55
+ # @api private
56
+ def header?
57
+ media? || title? || description?
58
+ end
59
+
60
+ # The media box's classes for the resolved media_variant.
61
+ # @api private
62
+ def media_classes
63
+ variant = media_variant == :icon ? :media_icon : :media_default
64
+ "#{css(:media)} #{css(variant)}"
65
+ end
66
+
67
+ # The empty-state root's attributes.
68
+ # @api private
69
+ def root_attributes
70
+ html_attributes.merge_if_not_set(
71
+ { "data-slot" => "empty" }.merge(component_data_attributes)
72
+ )
73
+ end
74
+
75
+ private :header?, :media_classes, :root_attributes
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Empty
6
+ # The Empty preview: the basic no-data state and the icon-tile
7
+ # variant with actions.
8
+ class Preview < Poetry::Core::Preview::Base
9
+ def default
10
+ render_component do |empty|
11
+ empty.with_title { "No projects yet" }
12
+ empty.with_description { "Get started by creating your first project." }
13
+ end
14
+ end
15
+
16
+ def with_icon_and_actions
17
+ render_component(media_variant: :icon) do |empty|
18
+ empty.with_media { embed(Poetry::Ui::Icon::Component.new(name: :folder)) }
19
+ empty.with_title { "No projects yet" }
20
+ empty.with_description { "Get started by creating your first project." }
21
+ embed(Poetry::Ui::Button::Component.new(size: :sm).with_content("Create project"))
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Empty
6
+ # Re-expressed through the cn-* theme layer. The title wears
7
+ # cn-font-heading (upstream parity): a no-op until a theme
8
+ # defines --font-heading - the sanctioned heading-font hook.
9
+ class Style < Poetry::Core::Style
10
+ base "cn-empty flex w-full min-w-0 flex-1 flex-col items-center justify-center " \
11
+ "text-center text-balance"
12
+
13
+ element :header, "cn-empty-header flex max-w-sm flex-col items-center"
14
+ # The media wrapper splits shared chrome from its two variants
15
+ # (default: transparent; icon: the rounded muted tile).
16
+ element :media, "cn-empty-media flex shrink-0 items-center justify-center " \
17
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0"
18
+ element :media_default, "cn-empty-media-default"
19
+ element :media_icon, "cn-empty-media-icon"
20
+ element :title, "cn-empty-title cn-font-heading"
21
+ element :description, "cn-empty-description text-muted-foreground [&>a]:underline " \
22
+ "[&>a]:underline-offset-4 [&>a:hover]:text-primary"
23
+ element :content, "cn-empty-content flex w-full max-w-sm min-w-0 flex-col items-center text-balance"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Gives an including component its family identity, derived from the
6
+ # namespace (Poetry::Ui::HoverCard::Component -> "HoverCard" /
7
+ # "hover-card"): the data-slot prefix, error nouns, and the sibling
8
+ # Style dictionary all resolve through it, so shared modules never
9
+ # hard-code a family name. Also supplies the root-shell attributes
10
+ # and the instance-id seed; a family with a different root or id
11
+ # shape simply overrides.
12
+ module FamilyIdentity
13
+ # The family root's data-slot, Stimulus, and self-identification
14
+ # attributes, merged under any caller-passed HTML attributes.
15
+ # @api private
16
+ def root_attributes
17
+ html_attributes.merge_if_not_set(
18
+ { "data-slot" => family_slot_prefix }
19
+ .merge(stimulus_attributes_for(:root))
20
+ .merge(component_data_attributes)
21
+ )
22
+ end
23
+
24
+ private
25
+
26
+ def family_namespace
27
+ self.class.module_parent
28
+ end
29
+
30
+ def family_name
31
+ @family_name ||= family_namespace.name.demodulize
32
+ end
33
+
34
+ def family_slot_prefix
35
+ @family_slot_prefix ||= family_name.underscore.dasherize
36
+ end
37
+
38
+ def family_style
39
+ family_namespace::Style
40
+ end
41
+
42
+ # Server-stable unique id seed (two of a family on one page must
43
+ # not share ids); portal-safe by convention - controllers resolve
44
+ # content via the id pair, never a Stimulus target.
45
+ def instance_id
46
+ @instance_id ||= poetry_instance_id("poetry-#{family_slot_prefix}")
47
+ end
48
+
49
+ private :root_attributes
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,19 @@
1
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
2
+ <% if label_text.present? %>
3
+ <% if group %>
4
+ <%= render Poetry::Ui::Label::Component.new(for_id: nil, id: label_id, class: css(:label),
5
+ data: { slot: "field-label" }).with_content(label_text) %>
6
+ <% else %>
7
+ <%= render Poetry::Ui::Label::Component.new(for_id: id, class: css(:label),
8
+ data: { slot: "field-label" }).with_content(label_text) %>
9
+ <% end %>
10
+ <% end %>
11
+ <% hint_body = hint_block ? capture(&hint_block) : hint %>
12
+ <% hint_tag = capture do %>
13
+ <% if hint_present? %><p id="<%= hint_id %>" data-slot="field-description" class="<%= css(:hint) %>"><%= hint_body %></p><% end %>
14
+ <% end %>
15
+ <%= hint_tag if hint_position == :above %>
16
+ <%= content %>
17
+ <%= hint_tag if hint_position == :below %>
18
+ <% if error.present? %><p id="<%= error_id %>" data-slot="field-error" class="<%= css(:error) %>"><%= error %></p><% end %>
19
+ <% end %>
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # The Field family - the label/control/hint/error wrapper for one form control.
6
+ module Field
7
+ # The Field wrapper - label, control, hint, and error as one unit
8
+ # with the aria wiring done for you: the control (the content
9
+ # block) receives its id from the field, and control_attributes
10
+ # carries aria-invalid + aria-describedby pointing at the hint and
11
+ # error ids. The FormBuilder composes this from model truth; Field
12
+ # itself is model-agnostic.
13
+ #
14
+ # @example
15
+ # render Poetry::Ui::Field::Component.new(
16
+ # id: "email", label_text: "Email", hint: "We never share it."
17
+ # ) do |field|
18
+ # tag.input(type: "email", name: "email", **field.control_attributes)
19
+ # end
20
+ class Component < Poetry::Core::Component
21
+ # The closed vocabulary for the orientation axis.
22
+ ORIENTATIONS = %i[vertical horizontal setting responsive].freeze
23
+
24
+ # The closed vocabulary for the hint_position axis.
25
+ HINT_POSITIONS = %i[below above].freeze
26
+
27
+ # Projected into the registry, llms.txt, and the agent surface.
28
+ AGENT_RULES = [
29
+ "Wire the control with field.control_attributes - never hand-write aria-describedby.",
30
+ "Error text arrives via error: (from model errors upstream) - never a bare red <p>.",
31
+ "hint: (escaped string) for pure data; with_hint { } for authored markup (links) - " \
32
+ "call it BEFORE the control so the hint id lands in aria-describedby.",
33
+ "orientation: :horizontal is the boolean-control layout (checkbox/switch left, " \
34
+ "label + hint stacked right) - text inputs and groups stay vertical.",
35
+ "orientation: :responsive stacks by default and flips label-left / control-right " \
36
+ "once its poetry_field_group container passes the md mark - the settings-page " \
37
+ "recipe (it needs that FieldGroup ancestor to measure against)."
38
+ ].freeze
39
+
40
+ style :orientation, default: :vertical, required: true, variants: ORIENTATIONS,
41
+ doc: "The layout axis. :horizontal is the boolean-control pattern: the control lands in " \
42
+ "the first grid column, label + hint/error stack in the second, and the control " \
43
+ "row-centers against the label line."
44
+
45
+ option :id, :string, required: true, doc: "The control's DOM id - the hint/error/label ids derive from it."
46
+ option :label_text, :string, doc: "The visible label text, associated with the control via for=."
47
+ option :hint, :string,
48
+ doc: "Plain-text guidance under the control (escaped wholesale); use with_hint for authored markup."
49
+ option :hint_position, :symbol, default: :below,
50
+ doc: "Where the hint renders relative to the control - :above puts guidance " \
51
+ "before a tall control. aria-describedby is identical either way; this " \
52
+ "is visual order only."
53
+ option :error, :string,
54
+ doc: "The error line (typically from model errors) - presence flips the invalid skin and leads the " \
55
+ "control's aria-describedby."
56
+ option :invalid, :boolean, default: false,
57
+ doc: "Flips the invalid skin (data-invalid + aria-invalid) WITHOUT an error line. " \
58
+ "error: implies it; use invalid: alone when the hint copy IS the " \
59
+ "requirement."
60
+ option :required, :boolean, default: false,
61
+ doc: "Marks the control required via aria-required only - never the native " \
62
+ "required attribute."
63
+ option :group, :boolean, default: false,
64
+ doc: "group: the control is a role-bearing <div> (RadioGroup, Slider) - label[for] " \
65
+ "would be inert (Chrome flags it), so the label drops for=, carries label_id, " \
66
+ "and control_attributes names the group via aria-labelledby (the visible " \
67
+ "label, i18n-proof)."
68
+
69
+ part "field", "The quartet's grid root - label, control, hint, and error stack inside",
70
+ states: {
71
+ "data-invalid" => { condition: "always - true when error: is present " \
72
+ "or invalid: is set, else false",
73
+ values: %w[true false] },
74
+ "data-orientation" => { condition: "always - the resolved orientation " \
75
+ "(horizontal is the boolean-control layout)",
76
+ values: ORIENTATIONS.map(&:to_s) }
77
+ }
78
+ part "field-label", "The Label (composed) wearing the source's field-label slot - names the control"
79
+ part "field-description", "The hint <p> (the source's description) - its id lands in the control's " \
80
+ "aria-describedby"
81
+ part "field-error", "The error <p> - present only when error: is set; its id leads " \
82
+ "the control's aria-describedby"
83
+ part "checkbox-input", "A nested Checkbox's hidden native input - the toggle renders " \
84
+ "as a wrapper-free fragment, so its sibling form store sits " \
85
+ "directly in the field's DOM (the horizontal boolean-control " \
86
+ "layout)"
87
+ part "switch-input", "A nested Switch's hidden native input - the same wrapper-free " \
88
+ "fragment escape as checkbox-input (the setting-row layout)"
89
+
90
+ # Validates hint_position and forces the content capture.
91
+ # @api private
92
+ def before_render
93
+ # Force the content block first: with_hint registers during the
94
+ # capture, and the template's hint tag must see it.
95
+ content
96
+
97
+ return if HINT_POSITIONS.include?(hint_position)
98
+
99
+ raise ArgumentError, "Field hint_position: #{hint_position.inspect} must be one of " \
100
+ "#{HINT_POSITIONS.inspect}"
101
+ end
102
+
103
+ # The hint element's id (referenced from aria-describedby).
104
+ # @api private
105
+ def hint_id = "#{id}-hint"
106
+ # The error element's id (leads aria-describedby).
107
+ # @api private
108
+ def error_id = "#{id}-error"
109
+ # The label element's id (referenced from aria-labelledby when group:).
110
+ # @api private
111
+ def label_id = "#{id}-label"
112
+
113
+ # Block-form hint for authored markup - a link or emphasis inside
114
+ # the guidance. Call it BEFORE the control renders, so the hint id
115
+ # lands in the control's aria-describedby; conflicts with hint: -
116
+ # use one or the other. The captured buffer renders as-is and is
117
+ # never re-blessed: ERB-authored markup stays markup, every
118
+ # interpolated value escapes normally, and a plain-String return
119
+ # is escaped by capture. Untrusted data belongs in hint: (escaped
120
+ # wholesale) or inside <%= %> in the block - never pre-marked
121
+ # html_safe.
122
+ #
123
+ # @example A hint containing a link
124
+ # <% field.with_hint do %>
125
+ # Forgot it? <%= link_to "Reset your password", reset_path %>
126
+ # <% end %>
127
+ def with_hint(&block)
128
+ raise ArgumentError, "Field with_hint conflicts with hint: - use one or the other" if hint.present?
129
+ if @control_attributes_issued
130
+ raise ArgumentError, "Field with_hint must be called before the control renders - " \
131
+ "control_attributes already handed out aria-describedby " \
132
+ "without the hint id"
133
+ end
134
+
135
+ @hint_block = block
136
+ self
137
+ end
138
+
139
+ # The captured hint block, consumed by the template.
140
+ # @api private
141
+ attr_reader :hint_block
142
+
143
+ # Whether any hint (string or block form) is present.
144
+ # @api private
145
+ def hint_present? = hint.present? || !@hint_block.nil?
146
+
147
+ # Whether the field wears the invalid skin (error: or invalid:).
148
+ # @api private
149
+ def invalid? = invalid || error.present?
150
+
151
+ # Everything the control inside the field must carry - the id,
152
+ # aria-describedby (error id first, then hint id), aria-invalid,
153
+ # aria-required, and aria-labelledby when group:. Merge it into
154
+ # the control's attributes (the FormBuilder does this for you).
155
+ def control_attributes
156
+ @control_attributes_issued = true
157
+ attrs = { "id" => id }
158
+ attrs["aria-labelledby"] = label_id if group && label_text.present?
159
+ describedby = []
160
+ describedby << error_id if error.present?
161
+ describedby << hint_id if hint_present?
162
+ attrs["aria-describedby"] = describedby.join(" ") if describedby.any?
163
+ attrs["aria-invalid"] = true if invalid?
164
+ # The aria-required-only rule: never the native required
165
+ # attribute - no native bubbles, no double announcement.
166
+ attrs["aria-required"] = true if required
167
+ attrs
168
+ end
169
+
170
+ # The quartet root's attributes.
171
+ # @api private
172
+ def root_attributes
173
+ html_attributes.merge_if_not_set(
174
+ { "data-slot" => "field", "data-invalid" => invalid?,
175
+ "data-orientation" => orientation }.merge(component_data_attributes)
176
+ )
177
+ end
178
+
179
+ private :hint_present?, :root_attributes
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,30 @@
1
+ <%# Shared template for every Field preview example: render the field with
2
+ a real Input wired through control_attributes - the canonical host
3
+ composition (see eval/arms/form_field/poetry.html.erb). group: wraps
4
+ the field in the FieldGroup @container scope responsive fields key on. %>
5
+ <% field_markup = capture do %>
6
+ <%= render component do |field| %>
7
+ <% if local_assigns[:hint_link] %>
8
+ <% field.with_hint do %>Short and lowercase - see the <a class="underline underline-offset-4" href="/docs/naming">naming guide</a>.<% end %>
9
+ <% end %>
10
+ <% if local_assigns[:checkbox_options] %>
11
+ <%= render Poetry::Ui::Checkbox::Component.new(**checkbox_options,
12
+ **field.control_attributes.transform_keys(&:to_sym)) %>
13
+ <% elsif local_assigns[:switch_options] %>
14
+ <%= render Poetry::Ui::Switch::Component.new(**switch_options,
15
+ **field.control_attributes.transform_keys(&:to_sym)) %>
16
+ <% else %>
17
+ <%= render Poetry::Ui::Input::Component.new(**input_options,
18
+ **field.control_attributes.transform_keys(&:to_sym)) %>
19
+ <% end %>
20
+ <% end %>
21
+ <% end %>
22
+ <div class="p-4">
23
+ <% if local_assigns[:group] %>
24
+ <%= render Poetry::Ui::FieldGroup::Component.new do %>
25
+ <%= field_markup %>
26
+ <% end %>
27
+ <% else %>
28
+ <%= field_markup %>
29
+ <% end %>
30
+ </div>
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Field
6
+ # The Field preview matrix - the label/control/hint/error quartet with
7
+ # the control wired through control_attributes (the aria plumbing the
8
+ # component exists to own). Rendering rides the sidecar preview.html.erb:
9
+ # nesting a control inside the field needs a real view context, and
10
+ # Preview#render inside a content block is the render_args DSL, not
11
+ # ActionView's render.
12
+ class Preview < Poetry::Core::Preview::Base
13
+ def default
14
+ render_with(component: Component.new(id: "field-email", label_text: "Email"),
15
+ input_options: { type: "email", name: "email" })
16
+ end
17
+
18
+ def with_hint
19
+ render_with(component: Component.new(id: "field-handle", label_text: "Handle",
20
+ hint: "Public, letters and dashes only."),
21
+ input_options: { name: "handle" })
22
+ end
23
+
24
+ def with_error
25
+ render_with(component: Component.new(id: "field-work-email", label_text: "Work email",
26
+ hint: "We never share it.", error: "can't be blank",
27
+ required: true),
28
+ input_options: { type: "email", name: "work_email" })
29
+ end
30
+
31
+ # The block-form hint: ERB-authored markup (the link) rides the
32
+ # same cn-field-description skin and aria-describedby wiring as
33
+ # the hint: option.
34
+ def with_hint_link
35
+ render_with(component: Component.new(id: "field-workspace", label_text: "Workspace"),
36
+ input_options: { name: "workspace" }, hint_link: true)
37
+ end
38
+
39
+ # The SETTING ROW: label + hint stacked left, control right and
40
+ # centered on the label line (upstream's content-first horizontal
41
+ # Field - the switch-description pattern).
42
+ def setting_with_switch
43
+ render_with(component: Component.new(id: "field-share", label_text: "Share across devices",
44
+ hint: "Focus is shared across devices, and turns " \
45
+ "off when you leave the app.",
46
+ orientation: :setting),
47
+ switch_options: { name: "share" })
48
+ end
49
+
50
+ # The container-driven layout: stacked by default, label-left /
51
+ # control-right once the FieldGroup container passes the md mark
52
+ # (the sidecar wraps this one in the FieldGroup @container scope).
53
+ def responsive
54
+ render_with(component: Component.new(id: "field-display-name", label_text: "Display name",
55
+ hint: "Shown on your public profile.",
56
+ orientation: :responsive),
57
+ input_options: { name: "display_name" }, group: true)
58
+ end
59
+
60
+ # The boolean-control layout: control left, label + hint stacked
61
+ # right, box centered on the label line (the checkbox/switch field
62
+ # pattern upstream demos with orientation=horizontal).
63
+ def horizontal_with_checkbox
64
+ render_with(component: Component.new(id: "field-newsletter", label_text: "Email newsletter",
65
+ hint: "Sent weekly. Unsubscribe anytime.",
66
+ orientation: :horizontal),
67
+ checkbox_options: { name: "newsletter", checked: true })
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Field
6
+ # Re-expressed through the cn-* theme layer. Poetry's Field is
7
+ # its own small shape (grid + hint + error); the cn names borrow
8
+ # upstream's vocabulary where semantics align (:hint wears
9
+ # cn-field-description - the style hook tracks upstream naming, the
10
+ # data-slot stays poetry's).
11
+ class Style < Poetry::Core::Style
12
+ base "cn-field grid group/field w-full"
13
+
14
+ # The grid re-flow is structural and rides the dictionary like
15
+ # upstream's cva (control auto-places into column 1, label pins to
16
+ # column 2 row 1, hint/error stack under it - DOM stays label-first
17
+ # for the for= association); the COLUMN GAP is design and lives
18
+ # theme-side under the cn name. Vertical is the base state and
19
+ # emits nothing (the marker/core-X empty-variant precedent).
20
+ variant :orientation, {
21
+ vertical: "",
22
+ horizontal: "cn-field-orientation-horizontal grid-cols-[auto_1fr] items-center " \
23
+ "[&>[data-slot=field-label]]:col-start-2 [&>[data-slot=field-label]]:row-start-1 " \
24
+ "[&>[data-slot=field-description]]:col-start-2 [&>[data-slot=field-error]]:col-start-2",
25
+ # The SETTING ROW (label + hint left, control right - upstream's
26
+ # content-first horizontal Field): the horizontal treatment
27
+ # mirrored, control auto-places beside the label line.
28
+ setting: "cn-field-orientation-horizontal grid-cols-[1fr_auto] items-center " \
29
+ "[&>[data-slot=field-label]]:col-start-1 [&>[data-slot=field-label]]:row-start-1 " \
30
+ "[&>[data-slot=field-description]]:col-start-1 [&>[data-slot=field-error]]:col-start-1",
31
+ # Horizontal mirrored and container-gated: stacked below the
32
+ # FieldGroup's md mark; above it the label + hint pair stacks
33
+ # tight in column 1 (the theme narrows the row gap to its
34
+ # cn-field-content value via the marker class) and the control
35
+ # spans both rows in column 2, centered against the whole pair
36
+ # - upstream's FieldContent geometry, expressed on poetry's
37
+ # flat quartet DOM. Outside a @container/field-group scope the
38
+ # query never fires and the field stays vertical (upstream
39
+ # parity: "container-aware parents").
40
+ # rubocop:disable Layout/LineLength -- each line is one indivisible
41
+ # utility token, and Tailwind scans this literal source (an
42
+ # interpolated shared prefix would silently drop the CSS).
43
+ responsive: "cn-field-orientation-responsive " \
44
+ "@md/field-group:grid-cols-[1fr_auto] " \
45
+ "@md/field-group:[&>[data-slot=field-label]]:col-start-1 " \
46
+ "@md/field-group:[&>[data-slot=field-label]]:row-start-1 " \
47
+ "@md/field-group:[&>[data-slot=field-label]]:self-center " \
48
+ "@md/field-group:[&>[data-slot=field-description]]:col-start-1 " \
49
+ "@md/field-group:[&>[data-slot=field-error]]:col-start-1 " \
50
+ "@md/field-group:[&>:not([data-slot=field-label],[data-slot=field-description],[data-slot=field-error])]:col-start-2 " \
51
+ "@md/field-group:[&>:not([data-slot=field-label],[data-slot=field-description],[data-slot=field-error])]:row-start-1 " \
52
+ "@md/field-group:[&>:not([data-slot=field-label],[data-slot=field-description],[data-slot=field-error])]:row-span-2 " \
53
+ "@md/field-group:[&>:not([data-slot=field-label],[data-slot=field-description],[data-slot=field-error])]:self-center"
54
+ # rubocop:enable Layout/LineLength
55
+ }
56
+
57
+ # The Label wears the source's field-label slot and its structural
58
+ # tokens on top of its own (a composed Label, the outer's part).
59
+ element :label, "cn-field-label group/field-label peer/field-label flex w-fit items-center " \
60
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col"
61
+ element :hint, "cn-field-description [&>a:hover]:text-primary [&>a]:underline " \
62
+ "[&>a]:underline-offset-4 font-normal group-has-data-horizontal/field:text-balance " \
63
+ "last:mt-0 leading-normal nth-last-2:-mt-1"
64
+ element :error, "cn-field-error font-normal"
65
+ end
66
+ end
67
+ end
68
+ end