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,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module FileInput
6
+ # Utility-only (the Separator/Spinner rule): the input variant wears
7
+ # the Input component's own chrome; the dropzone is structural
8
+ # utilities on semantic tokens, with the controller's data-dragging /
9
+ # data-populated (on the ROOT) driving the zone's states through
10
+ # group-data variants. data-slot selectors are the restyle seam.
11
+ class Style < Poetry::Core::Style
12
+ base "group/file-input flex w-full min-w-0 flex-col gap-2"
13
+
14
+ element :dropzone,
15
+ "flex min-h-32 w-full cursor-pointer flex-col items-center justify-center gap-1.5 " \
16
+ "rounded-lg border border-dashed border-input bg-transparent px-6 py-8 text-center " \
17
+ "transition-colors outline-none hover:bg-accent/30 " \
18
+ "has-focus-visible:border-ring has-focus-visible:ring-[3px] has-focus-visible:ring-ring/50 " \
19
+ "group-data-[dragging]/file-input:border-primary " \
20
+ "group-data-[dragging]/file-input:bg-accent/40"
21
+ element :dropzone_disabled, "pointer-events-none cursor-not-allowed opacity-50"
22
+ element :icon, "size-5 text-muted-foreground"
23
+ element :prompt, "text-sm font-medium"
24
+ element :hint, "text-xs text-muted-foreground"
25
+ element :list, "flex flex-col gap-1 text-sm empty:hidden " \
26
+ "[&>li]:flex [&>li]:items-baseline [&>li]:justify-between [&>li]:gap-4 " \
27
+ "[&>li]:rounded-md [&>li]:border [&>li]:px-3 [&>li]:py-1.5 " \
28
+ "[&_[data-slot=file-input-item-size]]:text-xs " \
29
+ "[&_[data-slot=file-input-item-size]]:text-muted-foreground"
30
+ element :clear, "self-start text-sm font-medium text-muted-foreground underline-offset-4 " \
31
+ "hover:text-foreground hover:underline"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,6 @@
1
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
2
+ <%= trigger %>
3
+ <%= content_tag(:div, **Poetry::Core::HTML::Attributes.new(content_attributes).to_attributes) do %>
4
+ <%= defer ? render(Poetry::Ui::Deferred::Component.new(src: defer)) { content } : content %>
5
+ <% end %>
6
+ <% end %>
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # The HoverCard family - the pointer-hover link preview.
6
+ module HoverCard
7
+ # The placement vocabularies - shared with every popup surface.
8
+ SIDES = Poetry::Ui::PopperConsumer::SIDES
9
+ # The closed vocabulary for the align axis.
10
+ ALIGNS = Poetry::Ui::PopperConsumer::ALIGNS
11
+
12
+ # A rich preview that opens when a pointer hovers a LINK - for
13
+ # sighted pointer users, BY DESIGN not an interaction path. Focus
14
+ # never moves into the card, and it presents NO aria surface (no
15
+ # haspopup/expanded/describedby, role-less content): advertising a
16
+ # keyboard-unreachable surface to assistive technology is worse
17
+ # than silence.
18
+ #
19
+ # THE REACHABLE-ELSEWHERE RULE (non-negotiable): everything in a
20
+ # hover card must exist at the trigger link's destination - the
21
+ # trigger stays an <a href> because it is simultaneously the no-JS
22
+ # fallback, the touch path, and the keyboard path.
23
+ #
24
+ # @example A profile preview behind a real link
25
+ # render Poetry::Ui::HoverCard::Component.new do |card|
26
+ # card.with_trigger(href: "/users/nextjs") { "@nextjs" }
27
+ # "Joined December 2021."
28
+ # end
29
+ class Component < Poetry::Core::Component
30
+ include Poetry::Ui::ComposableTrigger
31
+ include Poetry::Ui::PopperConsumer
32
+
33
+ # Projected into the registry, llms.txt, and the agent surface.
34
+ AGENT_RULES = [
35
+ ComposableTrigger::AGENT_RULE,
36
+ "Use poetry_hover_card - never hand-roll hover-div previews.",
37
+ "THE REACHABLE-ELSEWHERE RULE (non-negotiable): every piece of information in a hover card " \
38
+ "MUST exist at the trigger link's destination (or another keyboard/touch-reachable surface). " \
39
+ "The card is pointer-only enrichment - keyboard and touch users never see inside it.",
40
+ "The trigger must be a REAL link with a real href - it is the fallback, the touch path, and " \
41
+ "the keyboard path all at once. For a button LOOK, pass variant:/size: (renders through " \
42
+ "Button, still an <a> via href:) - never swap the tag to :button.",
43
+ "NO interactive elements inside the card - they get tabindex=-1 stripped and become " \
44
+ "pointer-only traps. Actions belong in a Popover or at the destination.",
45
+ "Don't add aria-expanded/haspopup to the trigger - advertising an unreachable surface is " \
46
+ "worse than silence.",
47
+ "Never use HoverCard for hints (Tooltip) or for content users act on (Popover).",
48
+ "Prefer defer: for expensive previews - a lazy turbo-frame that fetches on first open."
49
+ ].freeze
50
+
51
+ # The slots before_render enforces, stated statically for render-free checks.
52
+ REQUIRED_SLOTS = { trigger: "the enriched link" }.freeze
53
+
54
+ renders_one :trigger,
55
+ doc: "The enriched LINK: a real navigable <a> - THE no-JS fallback. tag: passthrough exists but " \
56
+ "change it knowingly (an <a> is the contract's fallback story). NO " \
57
+ "aria-haspopup/expanded/describedby - the card is invisible to the accessibility tree on " \
58
+ "purpose. Built as a lazy anatomy part (rendered at render time, not at with_trigger " \
59
+ "time). variant:/size: route through Button::Component - Button's href-implies-anchor " \
60
+ "keeps the trigger a REAL <a> wearing button styling, so the reachable-elsewhere contract " \
61
+ "holds.",
62
+ renders: lambda { |href: nil, tag: :a, **options, &block|
63
+ @trigger_href = href
64
+ attrs = {
65
+ "id" => trigger_id, "data-slot" => "hover-card-trigger"
66
+ }.merge(stimulus_attributes_for(:trigger))
67
+ # Trigger state: bare data-popup-open while open, NO attribute
68
+ # while closed (absence IS the state).
69
+ attrs["data-popup-open"] = "" if open
70
+ next composed_trigger(attrs, options, &block) if options[:compose]
71
+ if options.key?(:variant) || options.key?(:size)
72
+ next Button::Component.new(href: href, **attrs, **options, &block)
73
+ end
74
+
75
+ attrs["href"] = href if href.present?
76
+ Trigger.new(tag_name: tag, attributes: Poetry::Core::HTML::Attributes.merged(attrs, options))
77
+ }
78
+
79
+ use_stimulus do
80
+ on :root do
81
+ controller :hover_card do
82
+ register
83
+ value :open
84
+ value :open_delay
85
+ value :close_delay
86
+ end
87
+ controller :popper do
88
+ register
89
+ value :side
90
+ value :align
91
+ value :side_offset
92
+ value :align_offset
93
+ value :avoid_collisions
94
+ end
95
+ end
96
+ # The trigger handlers: pointerenter/leave pair
97
+ # timers (touch excluded), focus opens immediately / blur closes,
98
+ # and the touchstart guard (a tap navigates, never focus-opens).
99
+ on :trigger do
100
+ controller :hover_card do
101
+ action :pointer_enter, on: :pointerenter
102
+ action :pointer_leave, on: :pointerleave
103
+ action :focus_open, on: :focus
104
+ action :blur_close, on: :blur
105
+ action :touch_guard, on: :touchstart
106
+ end
107
+ controller(:popper) { target :anchor }
108
+ end
109
+ on :content do
110
+ controller(:popper) { target :content }
111
+ end
112
+ end
113
+
114
+ option :open, :boolean, default: false, doc: "Renders the card already open on page load."
115
+ option :defer, :string,
116
+ doc: "Defer the card body to a lazy turbo-frame. The panel is hidden until hover, so the fetch fires " \
117
+ "on first open for free; the component block (if any) becomes the frame's placeholder."
118
+ option :open_delay, :integer, default: 600, doc: "Hover-intent delay in ms before the card opens."
119
+ option :close_delay, :integer, default: 300,
120
+ doc: "Close-grace window in ms over the trigger+content pair."
121
+ # Placement defaults: bottom / center, 4px side offset.
122
+ popper_placement_options(side: :bottom, side_offset: 4)
123
+ option :content_class, :string,
124
+ doc: "The panel's class merge seam - e.g. content_class: \"w-80\" widens the card."
125
+
126
+ part "hover-card", "Root wrapper around the trigger link and the panel"
127
+ part "hover-card-trigger", "The enriched link itself - simultaneously the no-JS " \
128
+ "fallback, the touch path, and the keyboard path",
129
+ states: {
130
+ "data-popup-open" => "bare while the card is open; absent while closed " \
131
+ "(absence IS the closed state)"
132
+ }
133
+ part "hover-card-content", "The role-less preview panel (invisible to AT on purpose) - " \
134
+ "positioning, animation, and the open state ride here",
135
+ states: {
136
+ "data-open" => "card is open (the controller flips the pair at runtime)",
137
+ "data-closed" => "card is closed (the server-rendered state; hidden rides along)",
138
+ "data-side" => { condition: "always - the side (initial placement, re-resolved " \
139
+ "live by popper after flip)",
140
+ values: SIDES.map(&:to_s) },
141
+ "data-align" => { condition: "always - the alignment (re-resolved live by popper)",
142
+ values: ALIGNS.map(&:to_s) }
143
+ },
144
+ vars: Poetry::Ui::PopperConsumer.content_vars
145
+
146
+ # Enforces the required trigger; warns when it has no href.
147
+ # @api private
148
+ def before_render
149
+ raise ArgumentError, "HoverCard requires with_trigger (the enriched link)" unless trigger?
150
+
151
+ return if @trigger_href.present?
152
+
153
+ # Lint-level warning, not an error (anchors-without-href exist in
154
+ # legacy hosts): a trigger without a real destination defeats the
155
+ # reachable-elsewhere contract.
156
+ Rails.logger&.warn(
157
+ "poetry HoverCard: trigger has no href - the link IS the keyboard/touch/no-JS path " \
158
+ "(the reachable-elsewhere rule)"
159
+ )
160
+ end
161
+
162
+ # The preview panel's attributes.
163
+ # @api private
164
+ def content_attributes
165
+ attrs = {
166
+ "id" => content_id,
167
+ "data-slot" => "hover-card-content", (open ? "data-open" : "data-closed") => "",
168
+ # Initial placement, re-resolved live by popper on open.
169
+ "data-side" => side, "data-align" => align,
170
+ "class" => css(:content, class: content_class)
171
+ }.merge(stimulus_attributes_for(:content))
172
+ attrs["hidden"] = true unless open
173
+ attrs
174
+ end
175
+
176
+ private :content_attributes
177
+ end
178
+
179
+ # The trigger anatomy part - renders the chosen tag carrying
180
+ # pre-built wiring. internal_component!: full machinery, no
181
+ # registry entry (the trigger is anatomy, not a component).
182
+ #
183
+ # @api private
184
+ class Trigger < Poetry::Core::Component
185
+ internal_component!
186
+
187
+ # Carries pre-built wiring only. (@attributes is Component's
188
+ # ActiveModel storage - the wiring rides its own ivar.)
189
+ def initialize(tag_name:, attributes:)
190
+ super({})
191
+ @tag_name = tag_name
192
+ @trigger_attributes = attributes
193
+ end
194
+
195
+ def call
196
+ content_tag(@tag_name, content, @trigger_attributes)
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module HoverCard
6
+ # The HoverCard preview matrix: the ported hover-card-demo profile
7
+ # preview (the @nextjs card, w-80 override), the server-pinned open
8
+ # state, a placement sample, and custom delays. Every preview's card
9
+ # content is reachable at the trigger's href - the
10
+ # reachable-elsewhere audit runs against real destinations.
11
+ class Preview < Poetry::Core::Preview::Base
12
+ # The hover-card-demo port: a real profile link enriched with the
13
+ # profile preview. The trigger text is the link; the card is pure
14
+ # enhancement over navigation.
15
+ def default
16
+ profile_example
17
+ end
18
+
19
+ # Server-pinned open (controllable-state; the controller
20
+ # reconciles the layer + the tabindex strip on connect).
21
+ def open
22
+ profile_example(open: true)
23
+ end
24
+
25
+ # Placement sample (data-side re-resolves live on collision).
26
+ def side_right
27
+ render_component(side: :right, align: :start) do |card|
28
+ card.with_trigger(href: "https://github.com/vercel") { "@vercel" }
29
+ "Develop. Preview. Ship."
30
+ end
31
+ end
32
+
33
+ # Snappier delays for preview-dense surfaces (the docs recommend
34
+ # keeping open_delay >= ~300ms - accidental opens while mousing
35
+ # across text feel broken).
36
+ def fast
37
+ render_component(open_delay: 300, close_delay: 150) do |card|
38
+ card.with_trigger(href: "https://github.com/rails") { "@rails" }
39
+ "Ruby on Rails - the full-stack web framework."
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def profile_example(**)
46
+ render_component(content_class: "w-80", **) do |card|
47
+ card.with_trigger(href: "https://github.com/nextjs", class: "text-sm font-medium underline-offset-4 hover:underline") { "@nextjs" }
48
+ # No text size here - the card's type scale is theme-owned
49
+ # (.cn-hover-card-content; mira/lyra run text-xs/relaxed).
50
+ tag.div(class: "flex flex-col gap-1") do
51
+ tag.h4("@nextjs", class: "font-semibold") +
52
+ tag.p("The React Framework - created and maintained by @vercel.") +
53
+ tag.div("Joined December 2021", class: "text-xs text-muted-foreground")
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module HoverCard
6
+ # Re-expressed through the cn-* theme layer. One element: the
7
+ # preview panel on the popover token scheme; the origin class binds
8
+ # through tokens/aliases.css and stays inline. Width is theme-side
9
+ # (mira/rhea widen to w-72; every fragment owns it) -
10
+ # caller w-* classes still win from the utilities layer.
11
+ class Style < Poetry::Core::Style
12
+ element :content, "cn-hover-card-content z-50 " \
13
+ "origin-(--transform-origin) outline-hidden"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # The Icon family - inline SVG icons from the configured icon set.
6
+ module Icon
7
+ # The Icon component: template-less, rendering an icon's vendored,
8
+ # pre-sanitized inner markup from the configured icon set
9
+ # (`config.icon_library`, Lucide by default via poetry-lucide;
10
+ # override per render with `library:`).
11
+ #
12
+ # The ARIA contract:
13
+ # - `label:` given -> standalone/informative: role="img" + aria-label
14
+ # - no `label:` -> decorative: aria-hidden="true" + focusable="false"
15
+ #
16
+ # @example A decorative icon inside a labeled control
17
+ # render Poetry::Ui::Icon::Component.new(name: :plus)
18
+ class Component < Poetry::Core::Component
19
+ # Projected into the registry, llms.txt, and the agent surface.
20
+ AGENT_RULES = [
21
+ "Icons are decorative by default (aria-hidden); pass label: when the icon stands alone.",
22
+ "Never inline raw <svg> markup where an icon exists - use poetry_icon."
23
+ ].freeze
24
+
25
+ option :name, :symbol, required: true, format: :"icon-name",
26
+ doc: "The icon's name in the active set. format: :\"icon-name\" is the " \
27
+ "machine-readable value contract: the registry carries it, and literal names are " \
28
+ "validated against the icon set statically - a misspelled name is caught before " \
29
+ "it can crash a render."
30
+ option :label, :string,
31
+ doc: "The accessible name - given, the icon is standalone (role=img); absent, it is decorative " \
32
+ "(aria-hidden)."
33
+ option :library, :symbol, doc: "Per-render icon set override (defaults to config.icon_library)."
34
+
35
+ validate :icon_must_exist
36
+
37
+ part "icon", "The <svg> root itself - the vendored icon markup renders inside; " \
38
+ "ARIA (label: vs decorative) rides here"
39
+
40
+ # Renders the <svg> with the icon's inner markup.
41
+ # @api private
42
+ def call
43
+ # Vendored + sanitized at vendor time (the fetch pipeline) - the
44
+ # inner markup is trusted by construction; render never parses.
45
+ content_tag(:svg, resolved_markup.html_safe, **svg_attributes.to_attributes)
46
+ end
47
+
48
+ private
49
+
50
+ def icon_set
51
+ Poetry::Core::Icons.set(library)
52
+ end
53
+
54
+ # The missing-icon policy. poetry check catches literal
55
+ # names statically; a DYNAMIC name (a DB value, a user setting) only
56
+ # surfaces here - and a bad one should not 500 production. Dev/test
57
+ # keep the raise (config.raise_on_missing_icon nil = auto via
58
+ # Rails.env.local?); elsewhere the configured fallback renders and
59
+ # config.on_missing_icon fires (per render - dedup is the app's
60
+ # concern). A nil or itself-missing fallback re-raises the original.
61
+ def resolved_markup
62
+ icon_set.fetch(name)
63
+ rescue Poetry::Core::IconNotFound => e
64
+ raise if raise_on_missing_icon?
65
+
66
+ config = self.class.config
67
+ config.on_missing_icon&.call(name: name, library: library || config.icon_library, error: e)
68
+ fallback = config.icon_fallback
69
+ raise if fallback.nil?
70
+
71
+ begin
72
+ icon_set.fetch(fallback)
73
+ rescue Poetry::Core::IconNotFound
74
+ raise e
75
+ end
76
+ end
77
+
78
+ def raise_on_missing_icon?
79
+ configured = self.class.config.raise_on_missing_icon
80
+ return configured unless configured.nil?
81
+
82
+ !defined?(Rails.env) || Rails.env.local?
83
+ end
84
+
85
+ def icon_must_exist
86
+ return if name.blank? || icon_set.include?(name)
87
+
88
+ suggestion = Poetry::Core::Icons.suggest(name, icon_set.names)
89
+ hint = suggestion ? " - did you mean #{suggestion.to_sym.inspect}?" : ""
90
+ errors.add(:name, "unknown icon #{name.inspect} in the " \
91
+ "#{library || self.class.config.icon_library} set#{hint}")
92
+ end
93
+
94
+ def svg_attributes
95
+ html_attributes.merge_if_not_set(default_svg_attributes.merge(aria_attributes))
96
+ end
97
+
98
+ def default_svg_attributes
99
+ {
100
+ "xmlns" => "http://www.w3.org/2000/svg",
101
+ # Lucide's intrinsic box. Without it a standalone icon fills its
102
+ # container (a standalone icon once rendered 352px wide);
103
+ # inside components the [&_svg]:size-4 rules still win
104
+ # (CSS beats presentation attributes).
105
+ "width" => "24",
106
+ "height" => "24",
107
+ "viewBox" => "0 0 24 24",
108
+ "fill" => "none",
109
+ "stroke" => "currentColor",
110
+ "stroke-width" => "2",
111
+ "stroke-linecap" => "round",
112
+ "stroke-linejoin" => "round",
113
+ "data-slot" => "icon"
114
+ }.merge(component_data_attributes)
115
+ end
116
+
117
+ def aria_attributes
118
+ if label.present?
119
+ { "role" => "img", "aria-label" => label }
120
+ else
121
+ { "aria-hidden" => "true", "focusable" => "false" }
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Icon
6
+ class Preview < Poetry::Core::Preview::Base
7
+ def decorative
8
+ render_component(name: :plus)
9
+ end
10
+
11
+ def standalone_labeled
12
+ render_component(name: :trash, label: "Delete")
13
+ end
14
+
15
+ def chevron
16
+ render_component(name: :"chevron-right")
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # The Input family - the single-line native text control.
6
+ module Input
7
+ # The text Input - the styled native <input>. Template-less; error
8
+ # state is carried by aria-invalid (set by the Field/FormBuilder from
9
+ # model errors), which the classes style directly - state IS the
10
+ # accessibility attribute, never a parallel class.
11
+ #
12
+ # @example An email field
13
+ # render Poetry::Ui::Input::Component.new(type: "email", name: "email",
14
+ # placeholder: "you@example.com")
15
+ class Component < Poetry::Core::Component
16
+ # The mask controller identifier: masking is an Input OPTION,
17
+ # not a separate component.
18
+ MASK = %i[poetry core mask].freeze
19
+
20
+ # Projected into the registry, llms.txt, and the agent surface.
21
+ AGENT_RULES = [
22
+ "Inside a form, never render Input directly - use the FormBuilder's field (it wires ids, errors, and aria).",
23
+ "Error styling comes from aria-invalid, set from model errors - never hand-toggle error classes.",
24
+ "mask: formats as the user types ('(999) 999-9999'; 9=digit, a=letter, A=upper, " \
25
+ "*=alnum, #=sign/digit, \\\\ escapes, ? makes the rest optional) - the MASKED text " \
26
+ "submits; read data-raw for the bare value."
27
+ ].freeze
28
+
29
+ option :type, :string, default: "text", doc: "The native type attribute (text, email, password, file, ...)."
30
+ option :name, :string, doc: "The submitted param name."
31
+ option :value, :string, doc: "The current value."
32
+ option :placeholder, :string, doc: "Native placeholder text - not a substitute for a Label."
33
+ option :disabled, :boolean, default: false, doc: "Disables the native input."
34
+ option :invalid, :boolean, default: false,
35
+ doc: "Marks the input aria-invalid - the error skin keys on the attribute."
36
+ option :mask, :string,
37
+ doc: "Format-as-you-type mask descriptor ('(999) 999-9999'). Extra knobs (slot char, always-show, " \
38
+ "auto-clear) ride Stimulus values via data: - one declarative option covers the common case."
39
+
40
+ part "input", "The <input> element itself - no inner anatomy; error state is " \
41
+ "aria-invalid (set by Field/FormBuilder), never a parallel class",
42
+ states: {
43
+ "data-raw" => "mask: is set - the unmasked value, kept live by the mask " \
44
+ "controller (the masked text is what submits)"
45
+ }
46
+
47
+ # Renders the <input> element.
48
+ # @api private
49
+ def call
50
+ tag.input(**root_attributes.to_attributes)
51
+ end
52
+
53
+ # The <input> element's attributes.
54
+ # @api private
55
+ def root_attributes
56
+ attrs = { "type" => type, "data-slot" => "input" }.merge(component_data_attributes)
57
+ attrs["name"] = name if name.present?
58
+ attrs["value"] = value if value.present?
59
+ attrs["placeholder"] = placeholder if placeholder.present?
60
+ attrs["disabled"] = true if disabled
61
+ attrs["aria-invalid"] = true if invalid
62
+ attrs.merge!(mask_attributes) if mask.present?
63
+ html_attributes.merge_if_not_set(attrs)
64
+ end
65
+
66
+ private
67
+
68
+ def mask_attributes
69
+ attrs = Poetry::Core::HTML::Attributes.new
70
+ masked = Poetry::Core::Stimulus::Builder.new(MASK, attrs)
71
+ masked.register_controller
72
+ masked.with_value(:mask, mask)
73
+ attrs.to_attributes
74
+ end
75
+
76
+ private :root_attributes
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Input
6
+ # The Input preview matrix. Standalone inputs carry an aria-label so
7
+ # each state is accessible on its own; real forms wire the name via
8
+ # Field/FormBuilder instead (see the Field previews).
9
+ class Preview < Poetry::Core::Preview::Base
10
+ def default
11
+ render_component(type: "email", name: "email", placeholder: "you@example.com",
12
+ "aria-label" => "Email")
13
+ end
14
+
15
+ def with_value
16
+ render_component(name: "city", value: "Lisbon", "aria-label" => "City")
17
+ end
18
+
19
+ def disabled
20
+ render_component(name: "plan", value: "Enterprise", disabled: true, "aria-label" => "Plan")
21
+ end
22
+
23
+ def invalid
24
+ render_component(type: "email", name: "email", value: "not-an-email", invalid: true,
25
+ "aria-label" => "Email")
26
+ end
27
+
28
+ def masked
29
+ render_component(type: "tel", name: "phone", mask: "(999) 999-9999",
30
+ placeholder: "(555) 000-0000", "aria-label" => "Phone")
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Input
6
+ # Re-expressed through the cn-* theme layer: the field chrome
7
+ # (border, ring, invalid, dark treatments) rides themes/default.css.
8
+ # Placeholder color is theme-side: rhea's tinted field
9
+ # surface (bg-input/50) needs a darkened placeholder to hold AA, and
10
+ # an inline color would beat any theme's (layer order).
11
+ class Style < Poetry::Core::Style
12
+ base "cn-input w-full min-w-0 outline-none file:inline-flex file:border-0 " \
13
+ "file:bg-transparent file:text-foreground disabled:pointer-events-none " \
14
+ "disabled:cursor-not-allowed disabled:opacity-50"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ <%# A trailing action inside the field surface. %>
2
+ <div class="w-80 p-4">
3
+ <%= poetry_label(for_id: "input-group-url", class: "sr-only") { "Page URL" } %>
4
+ <%= poetry_input_group do %>
5
+ <%= poetry_input_group_input(id: "input-group-url", name: "url", value: "https://poetry.example") %>
6
+ <%= poetry_input_group_addon(align: :"inline-end") do %>
7
+ <%= poetry_input_group_button(size: :"icon-xs", label: "Copy URL") do %>
8
+ <%= poetry_icon(name: :copy) %>
9
+ <% end %>
10
+ <% end %>
11
+ <% end %>
12
+ </div>