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,263 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # A numeric input with steppers and display formatting.
6
+ module NumberField
7
+ # A numeric field with stepper buttons and locale-aware display
8
+ # formatting. The anatomy is a formatted visible <input type=text>
9
+ # (announced as "Number field") beside a visually-hidden
10
+ # <input type=number> that is the form and validation truth: the
11
+ # raw number submits (a currency field showing $1,234.50 submits
12
+ # 1234.5), and native required/min/max validation rides the hidden
13
+ # input.
14
+ #
15
+ # Visuals compose from existing primitives - the group wears
16
+ # InputGroup's chrome and the steppers are ghost icon Buttons - so
17
+ # themes restyle it through those components. The server renders
18
+ # the raw number; the display formats on connect. Typed input
19
+ # parses Latin digits (locale separators and currency/percent
20
+ # symbols are handled).
21
+ #
22
+ # @example
23
+ # render Poetry::Ui::NumberField::Component.new(
24
+ # name: "quantity", value: 2, min: 0, label: "Quantity"
25
+ # )
26
+ class Component < Poetry::Core::Component
27
+ include Poetry::Ui::InputGroupField
28
+
29
+ # Projected into the registry, llms.txt, and the agent surface.
30
+ AGENT_RULES = [
31
+ "Use poetry_number_field / form.number_field - never a hand-rolled spinner or a bare " \
32
+ "input type=number.",
33
+ "The server reads params[<name>] as the raw number string - display formatting " \
34
+ "(format:) never changes what submits.",
35
+ "Steppers are mouse/touch affordances (tabindex -1); keyboard users step with " \
36
+ "ArrowUp/Down (Shift = large_step, Alt = small_step) on the input itself.",
37
+ "Pair it with a Label/Field for the accessible name - the component ships none.",
38
+ "format: takes Intl.NumberFormatOptions as a Hash ({ style: \"currency\", " \
39
+ "currency: \"USD\" }); pick locale: to pin parsing separators."
40
+ ].freeze
41
+
42
+ use_stimulus do
43
+ on :root do
44
+ controller :number_field do
45
+ register
46
+ value :min, from: :min_number, if: -> { min.present? }
47
+ value :max, from: :max_number, if: -> { max.present? }
48
+ value :step, from: :step_number
49
+ value :large_step, from: :large_step_number
50
+ value :small_step, from: :small_step_number
51
+ value :snap, if: :snap
52
+ value :wheel, if: :wheel
53
+ value :format, from: :format_json, if: -> { format.present? }
54
+ value :locale, if: -> { locale.present? }
55
+ end
56
+ end
57
+ on :input do
58
+ controller :number_field do
59
+ target :input
60
+ action :keydown, on: :keydown
61
+ action :input, on: :input
62
+ action :focus, on: :focus
63
+ action :blur, on: :blur
64
+ end
65
+ end
66
+ # The form/validation truth: the sr-only native number input.
67
+ on :hidden do
68
+ controller :number_field do
69
+ target :hidden
70
+ action :hiddenChanged, on: :change
71
+ end
72
+ end
73
+ # One element per stepper direction, so stepper(direction)
74
+ # forwards stimulus_attributes_for(direction) into Button kwargs.
75
+ %i[increment decrement].each do |direction|
76
+ on direction do
77
+ controller :number_field do
78
+ target direction
79
+ action :press, on: :pointerdown
80
+ action :tap, on: :click
81
+ action :leave, on: :pointerleave
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ option :name, :string, required: true, doc: "The submitted field name - rides the hidden number input."
88
+ option :value, ActiveModel::Type::Value.new,
89
+ doc: "Initial value - a number; nil renders empty (null semantics)."
90
+ option :min, :float, doc: "The lower clamp for stepping and native validation."
91
+ option :max, :float, doc: "The upper clamp for stepping and native validation."
92
+ option :step, :float, default: 1.0, doc: "The arrow-key / stepper increment."
93
+ option :large_step, :float, default: 10.0, doc: "The Shift-arrow step size (the coarse jump)."
94
+ option :small_step, :float, default: 0.1, doc: "The Alt-arrow step size (the fine adjustment)."
95
+ option :snap, :boolean, default: false, doc: "Snaps stepped values to step multiples counted from min:."
96
+ option :wheel, :boolean, default: false, doc: "Opt-in wheel stepping while the input is focused."
97
+ option :format, ActiveModel::Type::Value.new,
98
+ doc: "Intl.NumberFormatOptions for the DISPLAY (submission stays raw)."
99
+ option :locale, :string,
100
+ doc: "Locale tag pinning the display and parsing separators; the page locale otherwise."
101
+ option :placeholder, :string, doc: "Placeholder text for the empty input."
102
+ option :disabled, :boolean, default: false, doc: "Disables both inputs and the steppers; the group chrome dims."
103
+ option :readonly, :boolean, default: false,
104
+ doc: "Makes the visible input read-only (steppers and typing inert)."
105
+ option :required, :boolean, default: false, doc: "Requires a value - native validation rides the hidden input."
106
+ option :invalid, :boolean, default: false,
107
+ doc: "Marks the field invalid (aria-invalid on the visible input; the group wears " \
108
+ "the destructive ring)."
109
+ option :id, :string, doc: "The visible input's dom id - the seam a Label's for_id: points at."
110
+ option :label, :string,
111
+ doc: "Standalone accessible name -> aria-label on the visible input. Inside a form, the Field label " \
112
+ "wires ids instead - pass neither and pair with poetry_label/form."
113
+ option :described_by, :string, doc: "aria-describedby wiring for Field hint/error pairing."
114
+
115
+ validates :step, numericality: { greater_than: 0 }
116
+
117
+ part "number-field", "Root wrapper - the controller, disabled/invalid/filled state, " \
118
+ "and the two-input pair ride here",
119
+ states: {
120
+ "data-disabled" => "disabled: is set (steppers disable, the group chrome dims)",
121
+ "data-invalid" => "invalid: is set (the group wears the destructive ring via " \
122
+ "the control's aria-invalid)",
123
+ "data-filled" => "the value is non-null (the controller keeps it live)"
124
+ }
125
+ part "number-field-group", "The bordered field surface - wears InputGroup's chrome " \
126
+ "(cn-input-group), focus ring keyed on the control inside"
127
+ part "input-group-addon", "The two stepper cells - InputGroup's addon vocabulary, " \
128
+ "reused so the group paddings compose",
129
+ states: {
130
+ "data-align" => { condition: "always - inline-start holds the decrement, " \
131
+ "inline-end the increment",
132
+ values: %w[inline-start inline-end] }
133
+ }
134
+ part "input-group-control", "The visible formatted <input type=text> - InputGroup's " \
135
+ "control slot (the themes' focus-ring hook); " \
136
+ "aria-roledescription \"Number field\", never a spinbutton"
137
+ # The steppers render as composed ghost Buttons and carry
138
+ # data-slot=number-field-increment/-decrement on Button's root -
139
+ # ownership attributes them to Button (the date-picker-trigger
140
+ # pattern), so they are documented here in prose only.
141
+
142
+ # Rejects a non-numeric value: early.
143
+ # @api private
144
+ def initialize(attributes = {})
145
+ super
146
+ return unless value.present? && !numeric?(value)
147
+
148
+ raise ArgumentError, "value: must be a number (got #{value.inspect})"
149
+ end
150
+
151
+ # Enforces the format: Hash contract.
152
+ # @api private
153
+ def before_render
154
+ return unless format.present? && !format.is_a?(Hash)
155
+
156
+ raise ArgumentError, "format: takes an Intl.NumberFormatOptions Hash"
157
+ end
158
+
159
+ # @api private
160
+ def root_attributes
161
+ attrs = {
162
+ "data-slot" => "number-field",
163
+ "class" => css
164
+ }.merge(component_data_attributes)
165
+ attrs["data-disabled"] = "" if disabled
166
+ attrs["data-invalid"] = "" if invalid
167
+ attrs["data-filled"] = "" if value.present?
168
+ html_attributes.merge_if_not_set(attrs.merge(stimulus_attributes_for(:root)))
169
+ end
170
+
171
+ # @api private
172
+ def input_attributes
173
+ attrs = Poetry::Core::HTML::Attributes.new(
174
+ "type" => "text",
175
+ "id" => control_id,
176
+ "data-slot" => "input-group-control",
177
+ "class" => Input::Style.css(class: InputGroup::Style.css(:control_input)),
178
+ "inputmode" => inputmode,
179
+ "autocomplete" => "off",
180
+ "autocorrect" => "off",
181
+ "spellcheck" => "false",
182
+ "aria-roledescription" => t("poetry.number_field.roledescription")
183
+ )
184
+ attrs["placeholder"] = placeholder if placeholder.present?
185
+ attrs["value"] = number(value) if value.present?
186
+ attrs["aria-label"] = label if label.present?
187
+ attrs["aria-invalid"] = "true" if invalid && !disabled
188
+ attrs["aria-describedby"] = described_by if described_by.present?
189
+ attrs["disabled"] = "" if disabled
190
+ attrs["readonly"] = "" if readonly
191
+ attrs["required"] = "" if required
192
+ attrs.merge!(stimulus_attributes_for(:input))
193
+ attrs
194
+ end
195
+
196
+ # The form/validation truth: raw number out, native constraint
197
+ # validation on. Focus never lands here (tabindex -1, aria-hidden).
198
+ # @api private
199
+ def hidden_attributes
200
+ attrs = Poetry::Core::HTML::Attributes.new(
201
+ "type" => "number",
202
+ "name" => name,
203
+ "class" => "sr-only",
204
+ "tabindex" => "-1",
205
+ "aria-hidden" => "true"
206
+ )
207
+ attrs["value"] = number(value) if value.present?
208
+ attrs["min"] = number(min) if min.present?
209
+ attrs["max"] = number(max) if max.present?
210
+ attrs["step"] = number(step)
211
+ attrs["disabled"] = "" if disabled
212
+ attrs["required"] = "" if required
213
+ attrs.merge!(stimulus_attributes_for(:hidden))
214
+ attrs
215
+ end
216
+
217
+ # The stepper Buttons (ghost, icon-xs, InputGroup's tiny-button
218
+ # chrome): tabindex -1 keeps them off the Tab order - keyboard
219
+ # users step on the input. aria-hidden is deliberately NOT applied,
220
+ # so touch screen readers can still activate them.
221
+ # @api private
222
+ def stepper(direction)
223
+ group_tool_button(slot: "number-field-#{direction}",
224
+ label: t("poetry.number_field.#{direction}"),
225
+ wiring: stimulus_attributes_for(direction))
226
+ end
227
+
228
+ # @api private
229
+ def stepper_icon(direction)
230
+ direction == :increment ? :plus : :minus
231
+ end
232
+
233
+ private
234
+
235
+ def numeric?(candidate)
236
+ candidate.is_a?(Numeric) || candidate.to_s.match?(/\A-?\d+(\.\d+)?\z/)
237
+ end
238
+
239
+ # Integer ranges starting at zero take the digit keyboard; decimals
240
+ # or open/negative ranges need the full layout.
241
+ def inputmode
242
+ whole = step == step.to_i && (!min.present? || min == min.to_i)
243
+ min.present? && min >= 0 && whole && format.blank? ? "numeric" : "decimal"
244
+ end
245
+
246
+ # Trailing-zero-free numeric strings (5.0 -> "5"), the slider idiom.
247
+ def number(numeric)
248
+ float = Float(numeric)
249
+ float == float.to_i ? float.to_i.to_s : float.to_s
250
+ end
251
+
252
+ def min_number = number(min)
253
+ def max_number = number(max)
254
+ def step_number = number(step)
255
+ def large_step_number = number(large_step)
256
+ def small_step_number = number(small_step)
257
+ def format_json = format.to_json
258
+
259
+ private :root_attributes, :input_attributes, :hidden_attributes, :stepper, :stepper_icon
260
+ end
261
+ end
262
+ end
263
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module NumberField
6
+ # @label Number Field
7
+ class Preview < Poetry::Core::Preview::Base
8
+ # @!group Basics
9
+
10
+ def default
11
+ render_component({ name: "quantity", label: "Quantity", value: 5, min: 0, max: 100 })
12
+ end
13
+
14
+ def empty
15
+ render_component({ name: "amount", label: "Amount", placeholder: "0" })
16
+ end
17
+
18
+ def decimal_steps
19
+ render_component({ name: "opacity", label: "Opacity", value: 0.5, min: 0, max: 1, step: 0.1 })
20
+ end
21
+
22
+ def snapped
23
+ render_component({ name: "duration", label: "Duration", value: 15, min: 0, max: 120, step: 15, snap: true })
24
+ end
25
+
26
+ # wheel: opts the focused input into scroll-to-step (off by
27
+ # default - a page-scroll trap otherwise).
28
+ def wheel_stepping
29
+ render_component({ name: "zoom", label: "Zoom", value: 100, min: 25, max: 400, step: 25, wheel: true })
30
+ end
31
+
32
+ # @!endgroup
33
+
34
+ # @!group Formatted
35
+
36
+ def currency
37
+ render_component({ name: "price", label: "Price", value: 1234.5, min: 0, step: 0.5,
38
+ format: { style: "currency", currency: "USD" }, locale: "en-US" })
39
+ end
40
+
41
+ def percent
42
+ render_component({ name: "ratio", label: "Ratio", value: 0.25, min: 0, max: 1, step: 0.05,
43
+ format: { style: "percent" }, locale: "en-US" })
44
+ end
45
+
46
+ # @!endgroup
47
+
48
+ # @!group States
49
+
50
+ def disabled
51
+ render_component({ name: "locked", label: "Locked quantity", value: 42, disabled: true })
52
+ end
53
+
54
+ def invalid
55
+ render_component({ name: "faulty", label: "Faulty quantity", value: 999, invalid: true })
56
+ end
57
+
58
+ def readonly
59
+ render_component({ name: "fixed", label: "Fixed quantity", value: 7, readonly: true })
60
+ end
61
+
62
+ # @!endgroup
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module NumberField
6
+ # Deliberately near-empty (the composition decision): the
7
+ # group wears InputGroup's chrome, the control wears Input's, the
8
+ # steppers wear Button's - zero new theme CSS. The root only stacks.
9
+ class Style < Poetry::Core::Style
10
+ base "flex w-full flex-col"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,40 @@
1
+ <%= content_tag(:nav, **root_attributes.to_attributes) do %>
2
+ <ul data-slot="pagination-content" class="<%= css(:content) %>">
3
+ <% if show_edges? %>
4
+ <li data-slot="pagination-item">
5
+ <%= render Poetry::Ui::Button::Component.new(**previous_options) do |button| %>
6
+ <% if icon_edges? %>
7
+ <%= render Poetry::Ui::Icon::Component.new(name: :"chevron-left", class: "cn-rtl-flip") %>
8
+ <% else %>
9
+ <% button.with_leading { render Poetry::Ui::Icon::Component.new(name: :"chevron-left", class: "cn-rtl-flip") } %>
10
+ <span class="<%= css(:label) %>"><%= previous_label %></span>
11
+ <% end %>
12
+ <% end %>
13
+ </li>
14
+ <% end %>
15
+ <% if pages %><% items.each do |item| %>
16
+ <li data-slot="pagination-item">
17
+ <% if item == :gap %>
18
+ <span aria-hidden="true" data-slot="pagination-ellipsis" class="<%= css(:ellipsis) %>">
19
+ <%= render Poetry::Ui::Icon::Component.new(name: :ellipsis) %>
20
+ <span class="sr-only">More pages</span>
21
+ </span>
22
+ <% else %>
23
+ <%= render(Poetry::Ui::Button::Component.new(**page_options(item))) { item.to_s } %>
24
+ <% end %>
25
+ </li>
26
+ <% end %><% end %>
27
+ <% if show_edges? %>
28
+ <li data-slot="pagination-item">
29
+ <%= render Poetry::Ui::Button::Component.new(**next_options) do |button| %>
30
+ <% if icon_edges? %>
31
+ <%= render Poetry::Ui::Icon::Component.new(name: :"chevron-right", class: "cn-rtl-flip") %>
32
+ <% else %>
33
+ <% button.with_trailing { render Poetry::Ui::Icon::Component.new(name: :"chevron-right", class: "cn-rtl-flip") } %>
34
+ <span class="<%= css(:label) %>"><%= next_label %></span>
35
+ <% end %>
36
+ <% end %>
37
+ </li>
38
+ <% end %>
39
+ </ul>
40
+ <% end %>
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Numbered page navigation.
6
+ module Pagination
7
+ # Numbered page navigation, data-driven: give it current + total
8
+ # and a path: callable (page -> url) and it owns the truncation
9
+ # math instead of making you compose every <li> - first and last
10
+ # always shown, a window of siblings around the current page,
11
+ # ellipses for the gaps. Renders a <nav> landmark of Button-styled
12
+ # links with the current page marked aria-current=page.
13
+ #
14
+ # @example Paginating a product list
15
+ # render Poetry::Ui::Pagination::Component.new(current: 3, total: 12,
16
+ # path: ->(page) { products_path(page: page) })
17
+ class Component < Poetry::Core::Component
18
+ # The closed vocabulary for the current_variant axis - how the
19
+ # current page link renders.
20
+ CURRENT_VARIANTS = %i[outline filled].freeze
21
+
22
+ # The closed vocabulary for the edges axis - the Previous/Next
23
+ # treatment.
24
+ EDGES = %i[labeled icons none].freeze
25
+
26
+ # Projected into the registry, llms.txt, and the agent surface.
27
+ AGENT_RULES = [
28
+ "poetry_pagination(current:, total:, path:) - never hand-build the <nav>/<ul>/<li> list.",
29
+ "path: is a callable ->(page) { url } (e.g. ->(p) { products_path(page: p) }).",
30
+ "The current page is aria-current=page; current_variant: :outline (the " \
31
+ "default) or :filled (the primary treatment - unambiguous active state); the rest are " \
32
+ "ghost links.",
33
+ "edges: :icons renders chevron-only Previous/Next (the table-footer posture); " \
34
+ ":none drops them for a bare page list; pages: false drops the numbers " \
35
+ "(pair with edges: :icons for the compact pager).",
36
+ "Host paginates with kaminari, pagy (v43+), or will_paginate? Run " \
37
+ "bin/rails g poetry:pagination (no argument = detect and install an adapter for " \
38
+ "each loaded gem) and keep calling paginate / poetry_pagy_nav / " \
39
+ "will_paginate(renderer: PoetryLinkRenderer) - never hand-wire poetry_pagination " \
40
+ "around a paginator gem."
41
+ ].freeze
42
+
43
+ option :current, :integer, required: true, doc: "The current page number (1-based)."
44
+ option :total, :integer, required: true, doc: "The total page count."
45
+ option :siblings, :integer, default: 1,
46
+ doc: "How many page links flank the current page before gaps elide to ellipses."
47
+ option :edges, :symbol, default: :labeled,
48
+ doc: "The Previous/Next treatment: :labeled (chevron + responsive text), :icons " \
49
+ "(chevron only - table footers), :none (no edge links)."
50
+ option :pages, :boolean, default: true,
51
+ doc: "Set false to drop the numbered links - the compact two-button pager (pair " \
52
+ "with edges: :icons)."
53
+ option :current_variant, :symbol, default: :outline,
54
+ doc: "How the current page link renders: :outline, or :filled for the " \
55
+ "primary Button treatment (an unambiguous active state)."
56
+ option :label, :string, default: "pagination", doc: "The nav landmark's accessible name."
57
+ option :previous_label, :string, default: "Previous",
58
+ doc: "The Previous link's visible text (hidden on narrow viewports)."
59
+ option :next_label, :string, default: "Next", doc: "The Next link's visible text (hidden on narrow viewports)."
60
+
61
+ validates :current_variant, inclusion: { in: CURRENT_VARIANTS }
62
+ validates :edges, inclusion: { in: EDGES }
63
+
64
+ # (pagination-link rides the composed Buttons, so those elements
65
+ # belong to Button's anatomy, not this contract.)
66
+ part "pagination", "The <nav> landmark (role=navigation, aria-label) around the page list"
67
+ part "pagination-content", "The <ul> holding every entry as one horizontal row"
68
+ part "pagination-item", "One <li> per entry - previous/next, a page link, or a gap"
69
+ part "pagination-ellipsis", "The elided-pages marker between windows - aria-hidden with an " \
70
+ "sr-only 'More pages'"
71
+
72
+ # Rejects the empty-nav combination.
73
+ # @api private
74
+ def before_render
75
+ return if pages || edges != :none
76
+
77
+ raise ArgumentError, "pagination with pages: false needs edges (:labeled or :icons) - " \
78
+ "edges: :none would render an empty nav"
79
+ end
80
+
81
+ # @api private
82
+ def show_edges? = edges != :none
83
+ # @api private
84
+ def icon_edges? = edges == :icons
85
+
86
+ # The page sequence with :gap markers where pages are elided. Small
87
+ # ranges show every page; larger ones show first, last, and a window
88
+ # of +/- siblings around current.
89
+ # @api private
90
+ def items
91
+ return (1..total).to_a if total <= 5 + (siblings * 2)
92
+
93
+ window = ((current - siblings)..(current + siblings)).select { |page| page > 1 && page < total }
94
+ with_gaps([1, *window, total])
95
+ end
96
+
97
+ # @api private
98
+ def current?(page) = page == current
99
+ # @api private
100
+ def path_for(page) = @path.call(page)
101
+
102
+ # Ghost for other pages; the current page renders per
103
+ # current_variant (:outline, or :filled for the primary Button).
104
+ # @api private
105
+ def page_variant(page)
106
+ return :ghost unless current?(page)
107
+
108
+ current_variant == :filled ? :default : :outline
109
+ end
110
+
111
+ # A numbered page link: aria-current for the current page, ghost
112
+ # otherwise; icon-sized. data-slot + data-active are the restyle hooks.
113
+ # @api private
114
+ def page_options(page)
115
+ {
116
+ tag: :a, href: path_for(page), size: :icon,
117
+ # A descriptive accessible name that still contains the visible
118
+ # number (WCAG label-in-name); the icon-sized Button requires it.
119
+ label: "Go to page #{page}",
120
+ variant: page_variant(page),
121
+ "aria-current": current?(page) ? "page" : nil,
122
+ data: { slot: "pagination-link", active: current?(page) }
123
+ }.compact
124
+ end
125
+
126
+ # Prev/Next: default-sized ghost links with the edge padding tweak;
127
+ # disabled (aria-disabled, no navigation) at the boundary.
128
+ # @api private
129
+ def edge_options(page, aria_label, padding)
130
+ options = {
131
+ tag: :a, href: path_for(page), variant: :ghost,
132
+ disabled: page < 1 || page > total,
133
+ data: { slot: "pagination-link" }
134
+ }
135
+ if icon_edges?
136
+ # Icon-sized Buttons take label: (the accessible-name contract).
137
+ options[:size] = :icon
138
+ options[:label] = aria_label
139
+ else
140
+ options[:size] = :default
141
+ options[:"aria-label"] = aria_label
142
+ options[:class] = padding
143
+ end
144
+ options
145
+ end
146
+
147
+ # @api private
148
+ def previous_options = edge_options(current - 1, "Go to previous page", css(:edge_previous))
149
+ # @api private
150
+ def next_options = edge_options(current + 1, "Go to next page", css(:edge_next))
151
+
152
+ # @api private
153
+ def root_attributes
154
+ html_attributes.merge_if_not_set(
155
+ { "role" => "navigation", "aria-label" => label, "data-slot" => "pagination" }
156
+ .merge(component_data_attributes)
157
+ )
158
+ end
159
+
160
+ private
161
+
162
+ # path: is passed as a keyword but held as a callable, not an option
163
+ # (options are typed data; a proc is behavior).
164
+ def initialize(path:, **)
165
+ super(**)
166
+ @path = path
167
+ end
168
+
169
+ def with_gaps(pages)
170
+ pages.each_with_object([]) do |page, sequence|
171
+ previous = sequence.reject { |item| item == :gap }.last
172
+ sequence << :gap if previous && page - previous > 1
173
+ sequence << page
174
+ end
175
+ end
176
+
177
+ private :show_edges?, :icon_edges?, :current?, :path_for, :page_variant, :page_options, :edge_options
178
+ private :previous_options, :next_options, :root_attributes
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Pagination
6
+ # The Pagination preview: a middle page of a long range (both ellipses
7
+ # show), a short range (every page, no truncation), and the first page
8
+ # (Previous disabled).
9
+ class Preview < Poetry::Core::Preview::Base
10
+ PATH = ->(page) { "?page=#{page}" }
11
+
12
+ def default
13
+ render_component(current: 4, total: 10, path: PATH)
14
+ end
15
+
16
+ def long_range_both_ellipses
17
+ render_component(current: 8, total: 20, path: PATH)
18
+ end
19
+
20
+ def short_range_no_truncation
21
+ render_component(current: 2, total: 4, path: PATH)
22
+ end
23
+
24
+ def first_page
25
+ render_component(current: 1, total: 10, path: PATH)
26
+ end
27
+
28
+ # The filled current-page treatment: the primary
29
+ # Button as the active marker - the data-index block's choice.
30
+ def filled_current
31
+ render_component(current: 4, total: 10, current_variant: :filled, path: PATH)
32
+ end
33
+
34
+ # edges: :none - the bare page list (upstream pagination-simple).
35
+ def simple_numbers_only
36
+ render_component(current: 2, total: 5, edges: :none, path: PATH)
37
+ end
38
+
39
+ # The compact two-button pager (upstream pagination-icons-only):
40
+ # chevron-only edges, no numbers.
41
+ def icons_only
42
+ render_component(current: 3, total: 10, edges: :icons, pages: false, path: PATH)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Pagination
6
+ # Re-expressed through the cn-* theme layer. The page links
7
+ # reuse Button styling; the nav root and the responsive label pair
8
+ # (hidden sm:block - a split-side responsive pair, kept together
9
+ # inline) stay utility-only.
10
+ class Style < Poetry::Core::Style
11
+ base "mx-auto flex w-full justify-center"
12
+
13
+ element :content, "cn-pagination-content flex items-center"
14
+ element :ellipsis, "cn-pagination-ellipsis flex items-center justify-center"
15
+ # The label span on prev/next: hidden below sm (icon-only on mobile).
16
+ element :label, "hidden sm:block"
17
+ # The edge-padding trim (upstream parity): the chevron side pulls
18
+ # in so the glyph's optical whitespace doesn't read as extra
19
+ # hover-box padding. Lives HERE because safelists harvest Style
20
+ # dictionaries and templates - a class string inside component.rb
21
+ # never compiles in any host.
22
+ element :edge_previous, "cn-pagination-previous"
23
+ element :edge_next, "cn-pagination-next"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
2
+ <%= anchor %>
3
+ <%= trigger %>
4
+ <%= content_tag(:div, **Poetry::Core::HTML::Attributes.new(content_attributes).to_attributes) do %>
5
+ <% if title? || description? %>
6
+ <div data-slot="popover-header" class="<%= css(:header) %>">
7
+ <% if title? %>
8
+ <div data-slot="popover-title" id="<%= title_id %>" class="<%= css(:title) %>"><%= title %></div>
9
+ <% end %>
10
+ <% if description? %>
11
+ <p data-slot="popover-description" id="<%= description_id %>" class="<%= css(:description) %>"><%= description %></p>
12
+ <% end %>
13
+ </div>
14
+ <% end %>
15
+ <%= content %>
16
+ <% end %>
17
+ <% end %>