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,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # The InputGroup family - one bordered field surface for a control plus addons.
6
+ module InputGroup
7
+ # The InputGroup - one bordered field surface holding a control plus
8
+ # addons (icons, text, kbd hints, tiny buttons). The GROUP wears the
9
+ # border and focus ring; the control inside is borderless
10
+ # (poetry_input_group_input / _textarea strip the Input's own chrome
11
+ # and stamp data-slot=input-group-control, which the group's
12
+ # focus-within/invalid selectors key on). Addons align inline
13
+ # (start/end) or block (start/end - full-width rows).
14
+ #
15
+ # @example
16
+ # <%= poetry_input_group do %>
17
+ # <%= poetry_input_group_addon do %>
18
+ # <%= poetry_icon(name: :search) %>
19
+ # <% end %>
20
+ # <%= poetry_input_group_input(name: "q", placeholder: "Search...") %>
21
+ # <% end %>
22
+ class Component < Poetry::Core::Component
23
+ requires_content "its control + addons"
24
+
25
+ # Projected into the registry, llms.txt, and the agent surface.
26
+ AGENT_RULES = [
27
+ "The control INSIDE must be poetry_input_group_input/_textarea - a plain poetry_input " \
28
+ "keeps its own border+ring and double-chromes the group.",
29
+ "Addons are poetry_input_group_addon(align:) wrapping icons/text/buttons; use " \
30
+ "poetry_input_group_text for muted captions and poetry_input_group_button for tiny actions.",
31
+ "The group is a surface, not a label - the control still needs its Label/Field pairing."
32
+ ].freeze
33
+
34
+ part "input-group", "The bordered group surface (role=group) - wears the border, the " \
35
+ "focus-within ring, and the invalid ring for the borderless " \
36
+ "control inside"
37
+ part "input-group-addon", "One addon cell (icons, text, kbd hints, tiny buttons) " \
38
+ "rendered by poetry_input_group_addon around the control",
39
+ states: {
40
+ "data-align" => { condition: "always - the addon's align: axis (inline rides " \
41
+ "the row, block takes a full-width row)",
42
+ values: %w[inline-start inline-end block-start block-end] }
43
+ }
44
+
45
+ # Enforces the required content block before render.
46
+ # @api private
47
+ def before_render
48
+ ensure_content!
49
+ end
50
+
51
+ # Renders the group surface around the content.
52
+ # @api private
53
+ def call
54
+ content_tag(:div, content, **root_attributes.to_attributes)
55
+ end
56
+
57
+ # The group surface's attributes.
58
+ # @api private
59
+ def root_attributes
60
+ html_attributes.merge_if_not_set(
61
+ { "role" => "group", "data-slot" => "input-group" }.merge(component_data_attributes)
62
+ )
63
+ end
64
+
65
+ private :root_attributes
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module InputGroup
6
+ # The InputGroup preview: the search pattern (icon + control + kbd),
7
+ # a button addon, and the block-aligned textarea form.
8
+ class Preview < Poetry::Core::Preview::Base
9
+ def default
10
+ render_with_template(template: "poetry/ui/input_group/search_preview")
11
+ end
12
+
13
+ def with_button
14
+ render_with_template(template: "poetry/ui/input_group/button_preview")
15
+ end
16
+
17
+ def textarea_block
18
+ render_with_template(template: "poetry/ui/input_group/textarea_preview")
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ <%# The search pattern: leading icon, borderless control, trailing kbd. %>
2
+ <div class="w-80 p-4">
3
+ <%= poetry_label(for_id: "input-group-search", class: "sr-only") { "Search" } %>
4
+ <%= poetry_input_group do %>
5
+ <%= poetry_input_group_addon do %>
6
+ <%= poetry_icon(name: :search) %>
7
+ <% end %>
8
+ <%= poetry_input_group_input(id: "input-group-search", name: "q", placeholder: "Search…") %>
9
+ <%= poetry_input_group_addon(align: :"inline-end") do %>
10
+ <%= poetry_kbd { "⌘K" } %>
11
+ <% end %>
12
+ <% end %>
13
+ </div>
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module InputGroup
6
+ # Re-expressed through the cn-* theme layer. The structural
7
+ # reflow chain (block addons force h-auto/flex-col) stays inline -
8
+ # it is layout mechanism; the chrome (border, focus-within ring,
9
+ # invalid ring, paddings) rides themes/default.css. Upstream's addon
10
+ # click-to-focus handler remains skipped (static port).
11
+ class Style < Poetry::Core::Style
12
+ base "cn-input-group group/input-group relative flex w-full min-w-0 items-center outline-none " \
13
+ "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col " \
14
+ "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col " \
15
+ "has-[>textarea]:h-auto"
16
+
17
+ # The addon: shared chrome + one align axis (inline = in the row,
18
+ # block = its own full-width row).
19
+ element :addon, "cn-input-group-addon flex cursor-text items-center justify-center select-none"
20
+ element :addon_inline_start, "cn-input-group-addon-align-inline-start order-first"
21
+ element :addon_inline_end, "cn-input-group-addon-align-inline-end order-last"
22
+ element :addon_block_start, "cn-input-group-addon-align-block-start order-first w-full justify-start"
23
+ element :addon_block_end, "cn-input-group-addon-align-block-end order-last w-full justify-start"
24
+
25
+ element :text, "cn-input-group-text flex items-center [&_svg]:pointer-events-none"
26
+
27
+ # The borderless control overrides (merged over Input/Textarea).
28
+ element :control_input, "cn-input-group-input flex-1"
29
+ element :control_textarea, "cn-input-group-textarea flex-1 resize-none"
30
+
31
+ # The tiny in-group Button sizes (merged over the ghost Button).
32
+ element :button, "cn-input-group-button flex items-center shadow-none"
33
+ element :button_xs, "cn-input-group-button-size-xs"
34
+ element :button_sm, "cn-input-group-button-size-sm"
35
+ element :button_icon_xs, "cn-input-group-button-size-icon-xs"
36
+ element :button_icon_sm, "cn-input-group-button-size-icon-sm"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,10 @@
1
+ <%# The block form: a textarea with a full-width footer row. %>
2
+ <div class="w-80 p-4">
3
+ <%= poetry_label(for_id: "input-group-notes", class: "sr-only") { "Notes" } %>
4
+ <%= poetry_input_group do %>
5
+ <%= poetry_input_group_textarea(id: "input-group-notes", name: "notes", placeholder: "Add a note…") %>
6
+ <%= poetry_input_group_addon(align: :"block-end") do %>
7
+ <%= poetry_input_group_text { "Markdown supported" } %>
8
+ <% end %>
9
+ <% end %>
10
+ </div>
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Gives a field component InputGroup's chrome: the control-id
6
+ # ladder, the bordered group shell, the addon cells, and the ghost
7
+ # tool-button recipe (steppers, clear, reveal, copy). Used by the
8
+ # affix-field controls (NumberField, SearchField, SensitiveInput);
9
+ # each includer keeps its own visible-input skeleton, since those
10
+ # attribute sets genuinely differ.
11
+ module InputGroupField
12
+ include FamilyIdentity
13
+
14
+ # The control-id ladder: a caller id wins, else the family
15
+ # instance seed.
16
+ # @api private
17
+ def control_id
18
+ @control_id ||= id.presence || instance_id
19
+ end
20
+
21
+ # The bordered role=group shell wearing InputGroup's chrome.
22
+ # @api private
23
+ def group_attributes
24
+ {
25
+ "role" => "group",
26
+ "data-slot" => "#{family_slot_prefix}-group",
27
+ "class" => InputGroup::Style.css
28
+ }
29
+ end
30
+
31
+ # One addon cell aligned inline-start or inline-end on the group row.
32
+ # @api private
33
+ def addon_attributes(align)
34
+ {
35
+ "data-slot" => "input-group-addon",
36
+ "data-align" => "inline-#{align}",
37
+ "class" => InputGroup::Style.css(:addon, class: InputGroup::Style.css(:"addon_inline_#{align}"))
38
+ }
39
+ end
40
+
41
+ private
42
+
43
+ # The ghost icon-xs tool button riding the group chrome (steppers,
44
+ # clear, reveal, copy). tabindex -1 by default (keyboard users act
45
+ # on the control); nil drops it (SensitiveInput's copy is a real
46
+ # tab stop).
47
+ def group_tool_button(slot:, label:, wiring:, tabindex: "-1", extra: {})
48
+ Button::Component.new({
49
+ variant: :ghost, size: :"icon-xs", disabled: disabled, label: label,
50
+ class: InputGroup::Style.css(:button, class: InputGroup::Style.css(:button_icon_xs)),
51
+ "data-slot" => slot,
52
+ "tabindex" => tabindex,
53
+ **extra,
54
+ "aria-controls" => control_id
55
+ }.compact.merge(wiring))
56
+ end
57
+
58
+ private :control_id, :group_attributes, :addon_attributes
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ <%# ONE real input over aria-hidden mirror cells. The char renders as the
2
+ slot's LEADING text node (the controller repaints the same node); the
3
+ caret element hides server-side - it exists only for the active EMPTY
4
+ cell under JS. Cells count from 0 across groups (slot order == string
5
+ index order; the row is dir=ltr). %>
6
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
7
+ <%= tag.input(**input_attributes.to_attributes) %>
8
+ <% cell = -1 %>
9
+ <% group_sizes.each_with_index do |group_size, group_index| %>
10
+ <div data-slot="input-otp-group" aria-hidden="true" class="<%= css(:group) %>">
11
+ <% group_size.times do %>
12
+ <% cell += 1 %>
13
+ <%= content_tag(:div, **slot_attributes(cell)) do %><%= char_at(cell) %><div data-slot="input-otp-caret" hidden class="<%= css(:caret) %>"><div class="<%= css(:caret_bar) %>"></div></div><% end %>
14
+ <% end %>
15
+ </div>
16
+ <% if separator && group_index < group_sizes.size - 1 %>
17
+ <%# aria-hidden DESPITE the source's bare role=separator - between
18
+ chunks of ONE field a semantic separator is AT noise (recorded
19
+ divergence; the role stays for styling/self-id parity). %>
20
+ <div data-slot="input-otp-separator" class="<%= css(:separator) %>" role="separator" aria-hidden="true">
21
+ <%= render Poetry::Ui::Icon::Component.new(name: :minus) %>
22
+ </div>
23
+ <% end %>
24
+ <% end %>
25
+ <% end %>
@@ -0,0 +1,250 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # The InputOtp family - fixed-length one-time-code entry.
6
+ module InputOtp
7
+ # Fixed-length one-time-code entry built around a single input:
8
+ # there are NO per-cell inputs. One real
9
+ # native <input> (autocomplete one-time-code) holds the whole value,
10
+ # stretched invisibly over the row, so paste, SMS autofill, IME,
11
+ # constraint validation and serialization are all native and
12
+ # assistive technology
13
+ # sees ONE text field. The n slot cells are a purely presentational
14
+ # aria-hidden MIRROR painted by JS: auto-advance and
15
+ # backspace-retreat are not features, they are the native caret
16
+ # PROJECTED (data-active follows selectionStart).
17
+ #
18
+ # The server renders the value's chars into the cells AND the input
19
+ # (no-JS honesty: typing/submitting works JS-free; only the live
20
+ # cell paint needs the controller). The cell row forces dir=ltr -
21
+ # codes are LTR strings, slot order == string index order even on
22
+ # RTL pages.
23
+ #
24
+ # @example
25
+ # render Poetry::Ui::InputOtp::Component.new(name: "code", length: 6, groups: [3, 3])
26
+ class Component < Poetry::Core::Component
27
+ # The supported code lengths - cell UIs degrade past ~8.
28
+ LENGTH_RANGE = (1..12)
29
+ # The built-in per-char filters with their native pattern + inputmode.
30
+ PATTERNS = {
31
+ digits: { js: "\\d", char: "[0-9]", inputmode: "numeric" },
32
+ alphanumeric: { js: "[a-zA-Z0-9]", char: "[a-zA-Z0-9]", inputmode: "text" }
33
+ }.freeze
34
+ # Field control_attributes land on the INPUT (the real control);
35
+ # everything else the caller passes styles the container.
36
+ INPUT_FACING = %w[id aria-label aria-describedby aria-invalid aria-required].freeze
37
+
38
+ # Projected into the registry, llms.txt, and the agent surface.
39
+ AGENT_RULES = [
40
+ "Use poetry_input_otp / form.otp_field - NEVER build per-cell inputs (n Tab stops, broken " \
41
+ "paste, broken SMS autofill, unnameable cells).",
42
+ "Label via Field always ('Verification code'); put the length in the hint.",
43
+ "groups must sum to length (ArgumentError).",
44
+ "Do NOT auto-submit on poetry:otp:complete without a visible confirm affordance - silent " \
45
+ "submit on the 6th keystroke strands users who mistyped char 3.",
46
+ "Never pre-fill value: with a real code in previews/test fixtures beyond dummies; never log " \
47
+ "the value (it is a live credential).",
48
+ "InputOTP is for CODES - passwords use Input type=password, longer identifiers use Input."
49
+ ].freeze
50
+
51
+ use_stimulus do
52
+ on :root do
53
+ controller :otp do
54
+ register
55
+ value :length
56
+ value :pattern, from: :pattern_js
57
+ # The input already covers the cells at z-20; this catches
58
+ # gap/separator clicks.
59
+ action :focus_input, on: :click
60
+ end
61
+ end
62
+ on :input do
63
+ controller :otp do
64
+ target :input
65
+ action :sync, on: %i[input focus blur]
66
+ # maxlength truncates RAW clipboard text before the input
67
+ # event - the controller filters the paste itself.
68
+ action :paste, on: :paste
69
+ end
70
+ end
71
+ on :slot do
72
+ controller(:otp) { target :slot }
73
+ end
74
+ end
75
+
76
+ option :name, :string, required: true, doc: "The ONE input serializes params[name] = the code string."
77
+ option :length, :integer, required: true, doc: "Code length = slot count = maxlength."
78
+ option :value, :string,
79
+ doc: "Current code (server-rendered into the input AND the cells). The FormBuilder deliberately never " \
80
+ "round-trips it (a rejected code is dead)."
81
+ option :groups, ActiveModel::Type::Value.new,
82
+ doc: "Cell clustering, e.g. [3, 3] -> two groups with a separator."
83
+ option :pattern, ActiveModel::Type::Value.new, default: :digits,
84
+ doc: ":digits (numeric keypad) | :alphanumeric | a custom " \
85
+ "Regexp - the per-char filter + the native pattern " \
86
+ "attribute + inputmode."
87
+ option :disabled, :boolean, default: false, doc: "Disables the native input (the whole row dims)."
88
+ option :required, :boolean, default: false,
89
+ doc: "aria-required on the input - never native required (the Field rule: " \
90
+ "required rides server-side validation + aria)."
91
+ option :invalid, :boolean, default: false,
92
+ doc: "aria-invalid on the input; the cells mirror the destructive treatment (set " \
93
+ "by Field/FormBuilder from the failed verify)."
94
+ option :separator, :boolean, default: true,
95
+ doc: "role=separator dash between groups (meaningful with 2+ groups)."
96
+
97
+ part "input-otp-container", "Root row (forced dir=ltr - slot order equals string index " \
98
+ "order even on RTL pages) wrapping the real input and the " \
99
+ "mirror cells"
100
+ part "input-otp", "THE real native <input> (autocomplete one-time-code) stretched " \
101
+ "invisibly over the row - the only AT and serialization surface"
102
+ part "input-otp-group", "One aria-hidden cluster of mirror cells (groups: clustering)"
103
+ part "input-otp-slot", "One presentational mirror cell - paints its char and the " \
104
+ "active-cell ring",
105
+ states: {
106
+ "data-active" => { condition: "\"true\" while the native caret sits on this cell " \
107
+ "(the controller projects selectionStart while the " \
108
+ "input is focused; the server renders \"false\")",
109
+ values: %w[true false] }
110
+ }
111
+ part "input-otp-caret", "The fake-caret overlay - hidden server-side; the controller " \
112
+ "unhides it on the active EMPTY cell"
113
+ part "input-otp-separator", "The between-groups dash - role=separator kept for parity " \
114
+ "but aria-hidden (a recorded divergence)"
115
+
116
+ # Validates length, groups, and pattern up front.
117
+ # @api private
118
+ def initialize(attributes = {})
119
+ super
120
+
121
+ validate_length!
122
+ validate_groups!
123
+ pattern_spec # raises on garbage patterns up front
124
+ end
125
+
126
+ # The resolved cell clustering ([length] when groups: is absent).
127
+ # @api private
128
+ def group_sizes
129
+ @group_sizes ||= (groups.presence || [length]).map { |size| Integer(size) }
130
+ end
131
+
132
+ # The value truncated to length, as rendered into input and cells.
133
+ # @api private
134
+ def display_value
135
+ value.to_s[0, length].to_s
136
+ end
137
+
138
+ # The character painted into the cell at index ("" when empty).
139
+ # @api private
140
+ def char_at(index)
141
+ display_value[index].to_s
142
+ end
143
+
144
+ # The native input's server-stable id.
145
+ # @api private
146
+ def input_id
147
+ @input_id ||= poetry_instance_id("poetry-input-otp")
148
+ end
149
+
150
+ # Whether the rendered value already fills every cell.
151
+ # @api private
152
+ def complete?
153
+ display_value.length >= length
154
+ end
155
+
156
+ # The row container's attributes.
157
+ # @api private
158
+ def root_attributes
159
+ attrs = {
160
+ "data-slot" => "input-otp-container",
161
+ # Codes are LTR strings: slot order must equal string index
162
+ # order even on RTL pages (the one place forcing ltr is right).
163
+ "dir" => "ltr"
164
+ }
165
+ container_attributes.merge_if_not_set(
166
+ attrs.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
167
+ )
168
+ end
169
+
170
+ # The real native input's attributes.
171
+ # @api private
172
+ def input_attributes
173
+ attrs = {
174
+ "type" => "text", "name" => name, "id" => input_id,
175
+ "maxlength" => length, "pattern" => html_pattern, "inputmode" => inputmode,
176
+ "autocomplete" => "one-time-code", "spellcheck" => "false",
177
+ "autocapitalize" => "off", "autocorrect" => "off",
178
+ "data-slot" => "input-otp", "class" => css(:input)
179
+ }
180
+ # The invisible input is the ONLY AT surface - it must always
181
+ # carry a name (the axe label rule); callers override via
182
+ # aria-label / Field labelling.
183
+ attrs["aria-label"] = t("poetry.input_otp.label")
184
+ attrs["value"] = display_value if display_value.present?
185
+ attrs["disabled"] = true if disabled
186
+ attrs["aria-required"] = true if required
187
+ attrs["aria-invalid"] = true if invalid
188
+ Poetry::Core::HTML::Attributes.new(html_attributes.slice(*INPUT_FACING))
189
+ .merge_if_not_set(attrs.merge(stimulus_attributes_for(:input)))
190
+ end
191
+
192
+ # One mirror cell's attributes.
193
+ # @api private
194
+ def slot_attributes(_index)
195
+ attrs = {
196
+ class: css(:slot), "data-slot" => "input-otp-slot", "data-active" => "false"
197
+ }
198
+ # Styling-only on an aria-hidden cell (the source's
199
+ # aria-invalid: variants key on it); AT never sees it.
200
+ attrs["aria-invalid"] = "true" if invalid
201
+ attrs.merge(stimulus_attributes_for(:slot))
202
+ end
203
+
204
+ private
205
+
206
+ # Everything NOT input-facing styles the container.
207
+ def container_attributes
208
+ Poetry::Core::HTML::Attributes.new(html_attributes.except(*INPUT_FACING))
209
+ end
210
+
211
+ def pattern_spec
212
+ return PATTERNS.fetch(pattern.to_sym) if pattern.respond_to?(:to_sym) && PATTERNS.key?(pattern.to_sym)
213
+ return { js: pattern.source, char: "(?:#{pattern.source})", inputmode: "text" } if pattern.is_a?(Regexp)
214
+
215
+ raise ArgumentError, "InputOTP pattern: must be :digits, :alphanumeric, or a Regexp - " \
216
+ "got #{pattern.inspect}"
217
+ end
218
+
219
+ def html_pattern
220
+ "#{pattern_spec[:char]}{#{length}}"
221
+ end
222
+
223
+ def inputmode
224
+ pattern_spec[:inputmode]
225
+ end
226
+
227
+ def validate_length!
228
+ return if LENGTH_RANGE.cover?(length)
229
+
230
+ raise ArgumentError, "InputOTP length: must be in #{LENGTH_RANGE} (cell UIs degrade past ~8) - " \
231
+ "got #{length.inspect}"
232
+ end
233
+
234
+ def validate_groups!
235
+ return if group_sizes.sum == length
236
+
237
+ raise ArgumentError, "InputOTP groups: must sum to length (#{length}) - " \
238
+ "#{group_sizes.inspect} sums to #{group_sizes.sum}"
239
+ rescue TypeError
240
+ raise ArgumentError, "InputOTP groups: must be an array of integers - got #{groups.inspect}"
241
+ end
242
+
243
+ def pattern_js = pattern_spec[:js]
244
+
245
+ private :group_sizes, :display_value, :char_at, :input_id, :complete?, :root_attributes, :input_attributes
246
+ private :slot_attributes
247
+ end
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module InputOtp
6
+ # The InputOTP preview matrix: the four ported examples (demo,
7
+ # pattern, controlled, separator) plus fill states, invalid, and
8
+ # disabled. Values are DUMMIES only - never a real code (the value
9
+ # is a live credential in production).
10
+ class Preview < Poetry::Core::Preview::Base
11
+ # @!group Groupings
12
+
13
+ # The input-otp-demo port: 6 digits as [3, 3] with a separator.
14
+ def default
15
+ render_component(name: "code", length: 6, groups: [3, 3],
16
+ "aria-label": "Verification code")
17
+ end
18
+
19
+ # One group of six (no separator rendered for a single group).
20
+ def single_group
21
+ render_component(name: "code", length: 6, "aria-label": "Verification code")
22
+ end
23
+
24
+ # The separator example's [2, 2, 2] shape.
25
+ def three_groups
26
+ render_component(name: "code", length: 6, groups: [2, 2, 2],
27
+ "aria-label": "Verification code")
28
+ end
29
+
30
+ # @!endgroup
31
+
32
+ # @!group Fill states
33
+
34
+ # Server-rendered partial value (no-JS honesty: the chars paint
35
+ # server-side; the caret cell needs the controller).
36
+ def partial
37
+ render_component(name: "code", length: 6, groups: [3, 3], value: "12",
38
+ "aria-label": "Verification code")
39
+ end
40
+
41
+ def complete
42
+ render_component(name: "code", length: 6, groups: [3, 3], value: "123456",
43
+ "aria-label": "Verification code")
44
+ end
45
+
46
+ # @!endgroup
47
+
48
+ # @!group States
49
+
50
+ # The alphanumeric pattern (input-otp-pattern parity):
51
+ # inputmode=text, autocapitalize off.
52
+ def alphanumeric
53
+ render_component(name: "code", length: 6, pattern: :alphanumeric,
54
+ "aria-label": "Verification code")
55
+ end
56
+
57
+ def disabled
58
+ render_component(name: "code", length: 6, groups: [3, 3], disabled: true,
59
+ "aria-label": "Verification code")
60
+ end
61
+
62
+ # The failed-verify re-render: aria-invalid on the input, the
63
+ # destructive treatment on the cells, value BLANKED (the
64
+ # otp_field default - a rejected code is dead).
65
+ def invalid
66
+ render_component(name: "code", length: 6, groups: [3, 3], invalid: true,
67
+ "aria-label": "Verification code")
68
+ end
69
+
70
+ # @!endgroup
71
+
72
+ # @!group Recipes
73
+
74
+ # The Field recipe: label 'Verification code', length in the hint,
75
+ # id/describedby landing on the INPUT (the real control).
76
+ def in_a_field
77
+ field = Field::Component.new(
78
+ id: "preview-code", label_text: "Verification code",
79
+ hint: "Enter the 6-digit code we sent to your phone."
80
+ )
81
+ render_component(field) do
82
+ embed(Component.new(name: "code", length: 6, groups: [3, 3],
83
+ **field.control_attributes.transform_keys(&:to_sym)))
84
+ end
85
+ end
86
+
87
+ # @!endgroup
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module InputOtp
6
+ # Re-expressed through the cn-* theme layer. The invisible-
7
+ # control mechanism stays ENTIRELY inline (:input stretched over the
8
+ # slot row at opacity 0.005 - clickable, focusable, AT-visible; NEVER
9
+ # sr-only) as does the caret overlay geometry + blink motion; the
10
+ # cell chrome (borders, active ring, invalid) rides
11
+ # .cn-input-otp-slot in themes/default.css.
12
+ class Style < Poetry::Core::Style
13
+ base "cn-input-otp relative flex items-center has-disabled:opacity-50"
14
+
15
+ # THE control: one real native input, full-length value, stretched
16
+ # invisibly over the slot row.
17
+ element :input, "absolute inset-0 z-20 h-full w-full opacity-[0.005] text-transparent " \
18
+ "caret-transparent outline-none selection:bg-transparent " \
19
+ "disabled:cursor-not-allowed"
20
+
21
+ element :group, "cn-input-otp-group flex items-center"
22
+ element :separator, "cn-input-otp-separator flex items-center"
23
+
24
+ element :slot, "cn-input-otp-slot relative flex items-center justify-center " \
25
+ "data-[active=true]:z-10"
26
+
27
+ # The fake-caret overlay + the blinking bar (visible only on the
28
+ # active EMPTY cell; steady under prefers-reduced-motion via the
29
+ # utilities layer).
30
+ element :caret, "pointer-events-none absolute inset-0 flex items-center justify-center"
31
+
32
+ element :caret_bar, "cn-input-otp-caret-line animate-caret-blink duration-1000"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,13 @@
1
+ <%= content_tag(tag, **root_attributes.to_attributes) do %>
2
+ <% if header? %><div data-slot="item-header" class="<%= css(:header) %>"><%= header %></div><% end %>
3
+ <% if media? %><div data-slot="item-media" data-variant="<%= media_variant %>" class="<%= media_classes %>"><%= media %></div><% end %>
4
+ <% if content_column? %>
5
+ <div data-slot="item-content" class="<%= css(:content) %>">
6
+ <% if title? %><div data-slot="item-title" class="<%= css(:title) %>"><%= title %></div><% end %>
7
+ <% if description? %><p data-slot="item-description" class="<%= css(:description) %>"><%= description %></p><% end %>
8
+ <%= content %>
9
+ </div>
10
+ <% end %>
11
+ <% if actions? %><div data-slot="item-actions" class="<%= css(:actions) %>"><%= actions %></div><% end %>
12
+ <% if footer? %><div data-slot="item-footer" class="<%= css(:footer) %>"><%= footer %></div><% end %>
13
+ <% end %>