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,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Sidebar
6
+ # Style dictionary for the Sidebar family. The collapse / rail /
7
+ # inset geometry chains (width vars, offcanvas math, icon-mode
8
+ # size! pads, peer-size action tops) stay ENTIRELY inline - the
9
+ # machinery a swapped theme must never break; surfaces, tints, and
10
+ # type ride the theme. The mobile <dialog> keeps m-0 and the side
11
+ # margins BOTH inline so the class merger keeps collapsing them.
12
+ class Style < Poetry::Core::Style
13
+ base ""
14
+
15
+ # The provider wrapper carries the width custom properties.
16
+ # group/sidebar-wrapper has an in-repo consumer: the app-shell
17
+ # block's topbar answers the collapsed state
18
+ # (group-has-data-[collapsible=icon]/sidebar-wrapper)
19
+ # - the upstream site-header pattern. Two upstream markers stay
20
+ # dropped: group/menu-button's upstream consumer is a collapsible
21
+ # menu-button composition poetry does not ship yet, and
22
+ # group/menu-sub-item has no consumer anywhere in the current
23
+ # upstream tree.
24
+ element :wrapper, "group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar"
25
+
26
+ # The peer group (the desktop shell; below md the mobile <dialog>
27
+ # takes over).
28
+ element :peer, "group peer hidden text-sidebar-foreground md:block"
29
+
30
+ # The mobile sheet: open:flex not flex (the Dialog lesson).
31
+ element :mobile, "cn-sidebar-mobile relative m-0 open:flex h-full max-h-none " \
32
+ "w-(--sidebar-width) max-w-none flex-col md:hidden"
33
+ element :mobile_left, "cn-sidebar-mobile-left mr-auto"
34
+ element :mobile_right, "cn-sidebar-mobile-right ml-auto"
35
+ element :mobile_inner, "flex h-full w-full flex-col"
36
+
37
+ # The mobile side's edge classes for the <dialog>.
38
+ def self.mobile_side(value)
39
+ resolver.render(:"mobile_#{value}")
40
+ end
41
+
42
+ # The desktop gap that pushes the inset over.
43
+ element :gap, "cn-sidebar-gap relative w-(--sidebar-width) bg-transparent " \
44
+ "group-data-[collapsible=offcanvas]:w-0 group-data-[side=right]:rotate-180 " \
45
+ "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
46
+ element :gap_inset, "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
47
+
48
+ element :container,
49
+ "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] " \
50
+ "duration-200 ease-linear data-[side=left]:left-0 " \
51
+ "data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] " \
52
+ "data-[side=right]:right-0 " \
53
+ "data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] " \
54
+ "md:flex group-data-[collapsible=icon]:w-(--sidebar-width-icon) " \
55
+ "group-data-[side=left]:border-r group-data-[side=right]:border-l"
56
+ element :container_inset,
57
+ "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
58
+
59
+ element :inner, "cn-sidebar-inner flex size-full flex-col"
60
+
61
+ element :inset, "cn-sidebar-inset relative flex w-full flex-1 flex-col"
62
+
63
+ # The rail (desktop-only click strip).
64
+ element :rail,
65
+ "cn-sidebar-rail absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear " \
66
+ "group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 " \
67
+ "after:start-1/2 after:w-[2px] sm:flex " \
68
+ "group-data-[collapsible=offcanvas]:translate-x-0 " \
69
+ "group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar"
70
+
71
+ element :header, "cn-sidebar-header flex flex-col"
72
+ element :footer, "cn-sidebar-footer flex flex-col"
73
+ element :separator, "cn-sidebar-separator w-auto"
74
+ element :content, "cn-sidebar-content no-scrollbar flex min-h-0 flex-1 flex-col overflow-auto " \
75
+ "group-data-[collapsible=icon]:overflow-hidden"
76
+
77
+ element :group, "cn-sidebar-group relative flex w-full min-w-0 flex-col"
78
+ element :group_label, "cn-sidebar-group-label flex shrink-0 items-center outline-hidden " \
79
+ "transition-[margin,opacity] duration-200 ease-linear " \
80
+ "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 " \
81
+ "[&>svg]:shrink-0"
82
+ element :group_content, "cn-sidebar-group-content w-full"
83
+
84
+ element :menu, "cn-sidebar-menu flex w-full min-w-0 flex-col"
85
+ element :menu_item, "group/menu-item relative"
86
+ element :menu_button,
87
+ "cn-sidebar-menu-button peer/menu-button flex w-full items-center overflow-hidden " \
88
+ "text-left outline-hidden transition-[width,height,padding] " \
89
+ "group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! " \
90
+ "disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none " \
91
+ "aria-disabled:opacity-50 [&_svg]:shrink-0 [&>span:last-child]:truncate " \
92
+ "[&_svg]:size-4 group/menu-button"
93
+ element :menu_button_default, "cn-sidebar-menu-button-size-default"
94
+
95
+ # The item-corner action + badge - positions and
96
+ # reveal machinery inline, tints/type themed.
97
+ element :menu_action,
98
+ "cn-sidebar-menu-action absolute top-1.5 right-1 flex aspect-square items-center " \
99
+ "justify-center outline-hidden transition-transform " \
100
+ "group-data-[collapsible=icon]:hidden " \
101
+ "peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 " \
102
+ "peer-data-[size=sm]/menu-button:top-1 after:absolute after:-inset-2 " \
103
+ "md:after:hidden [&>svg]:shrink-0"
104
+ element :menu_action_hover,
105
+ "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 " \
106
+ "peer-data-active/menu-button:text-sidebar-accent-foreground aria-expanded:opacity-100 " \
107
+ "md:opacity-0"
108
+ # Dictionary-held surfaces so the safelist harvests the classes
109
+ # (helper strings are invisible to it).
110
+ element :group_action_button, "cn-sidebar-group-action"
111
+ element :input_control, "cn-sidebar-input"
112
+ element :menu_skeleton, "cn-sidebar-menu-skeleton flex items-center"
113
+ element :menu_skeleton_icon, "cn-sidebar-menu-skeleton-icon"
114
+ element :menu_skeleton_text, "cn-sidebar-menu-skeleton-text max-w-(--skeleton-width) flex-1"
115
+
116
+ element :menu_badge,
117
+ "cn-sidebar-menu-badge pointer-events-none absolute right-1 flex items-center " \
118
+ "justify-center select-none group-data-[collapsible=icon]:hidden " \
119
+ "peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 " \
120
+ "peer-data-[size=sm]/menu-button:top-1 tabular-nums"
121
+ element :menu_button_sm, "cn-sidebar-menu-button-size-sm"
122
+ element :menu_button_lg, "cn-sidebar-menu-button-size-lg group-data-[collapsible=icon]:p-0!"
123
+
124
+ element :menu_sub, "cn-sidebar-menu-sub mx-3.5 flex min-w-0 translate-x-px flex-col " \
125
+ "group-data-[collapsible=icon]:hidden"
126
+ element :menu_sub_item, "relative"
127
+ element :menu_sub_button, "cn-sidebar-menu-sub-button flex min-w-0 -translate-x-px items-center " \
128
+ "overflow-hidden outline-hidden disabled:pointer-events-none " \
129
+ "disabled:opacity-50 aria-disabled:pointer-events-none " \
130
+ "aria-disabled:opacity-50 [&>svg]:shrink-0 [&>span:last-child]:truncate"
131
+
132
+ # The size-variant classes for a menu button.
133
+ def self.menu_button_size(value)
134
+ css(:"menu_button_#{value}")
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # A pulsing placeholder box shown while content loads.
6
+ module Skeleton
7
+ # A pulsing placeholder while content loads. Size it with
8
+ # utility classes (h-4 w-32, size-10 rounded-full, ...); the content
9
+ # block is optional (usually empty - the box IS the placeholder).
10
+ #
11
+ # @example An avatar-and-line placeholder pair
12
+ # render Poetry::Ui::Skeleton::Component.new(class: "size-10 rounded-full")
13
+ # render Poetry::Ui::Skeleton::Component.new(class: "h-4 w-32")
14
+ class Component < Poetry::Core::Component
15
+ # Projected into the registry, llms.txt, and the agent surface.
16
+ AGENT_RULES = [
17
+ "Skeleton is a loading placeholder - size it with classes (h-4 w-32); it has no content of its own.",
18
+ "Mark the live region that will replace it (aria-busy on the container), not the skeleton."
19
+ ].freeze
20
+
21
+ part "skeleton", "The pulsing placeholder box itself - sized entirely by utility classes"
22
+
23
+ # @api private
24
+ def call
25
+ content_tag(:div, content, **root_attributes.to_attributes)
26
+ end
27
+
28
+ # @api private
29
+ def root_attributes
30
+ html_attributes.merge_if_not_set(
31
+ { "data-slot" => "skeleton" }.merge(component_data_attributes)
32
+ )
33
+ end
34
+
35
+ private :root_attributes
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Skeleton
6
+ # The Skeleton preview: placeholders in the common shapes.
7
+ class Preview < Poetry::Core::Preview::Base
8
+ def line
9
+ render_component(class: "h-4 w-48")
10
+ end
11
+
12
+ def avatar
13
+ render_component(class: "size-12 rounded-full")
14
+ end
15
+
16
+ def block
17
+ render_component(class: "h-24 w-full rounded-xl")
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Skeleton
6
+ # Style dictionary for the Skeleton family: the pulse is the
7
+ # mechanism (inline), the box treatment is the theme's.
8
+ class Style < Poetry::Core::Style
9
+ base "cn-skeleton animate-pulse"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ <%# Track + range ride the geometry vars; each thumb sits in an
2
+ absolutely positioned anchor (Radix's thumb-wrapper span, poetry
3
+ edition) with its hidden input alongside - thumbs and inputs stay
4
+ index-aligned with the value array. %>
5
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
6
+ <%= content_tag(:div, content_tag(:div, nil, **range_attributes), **track_attributes) %>
7
+ <% thumb_values.each_index do |index| %>
8
+ <%= content_tag(:span, **anchor_attributes(index)) do %>
9
+ <%= content_tag(:div, nil, **thumb_attributes(index)) %>
10
+ <%= tag.input(**input_attributes(index)) %>
11
+ <% end %>
12
+ <% end %>
13
+ <% end %>
@@ -0,0 +1,360 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # A draggable numeric value (or range) on a continuous track.
6
+ module Slider
7
+ # A numeric value (or a [low, high] range - two thumbs) picked by
8
+ # dragging along a continuous track. Reach for it when the value is
9
+ # approximate by nature; precise known-number entry belongs in a
10
+ # number input. Every thumb is its own Tab stop, and arrow keys move
11
+ # the focused thumb by step:.
12
+ #
13
+ # The server renders everything the controller would: aria-value*
14
+ # per thumb (range bounds clamped to the neighboring thumb), the
15
+ # --slider-start/--slider-end geometry vars (no first-paint jump),
16
+ # and one hidden <input type=hidden> per thumb - name single,
17
+ # name[] range, which is precisely Rails' array-param convention
18
+ # (params: "50" / ["200", "800"]). Change fires per mutation,
19
+ # commit once per gesture; native input/change fire on COMMIT only.
20
+ #
21
+ # @example
22
+ # render Poetry::Ui::Slider::Component.new(name: "volume", value: 50, label: "Volume")
23
+ class Component < Poetry::Core::Component
24
+ # The closed vocabulary for the axis option.
25
+ ORIENTATIONS = %i[horizontal vertical].freeze
26
+
27
+ # Projected into the registry, llms.txt, and the agent surface.
28
+ AGENT_RULES = [
29
+ "Use poetry_slider / form.slider - never hand-roll a draggable div.",
30
+ "Every thumb MUST have a distinct accessible name (label: - array of two for ranges). " \
31
+ "ArgumentError otherwise.",
32
+ "Give value_text: whenever the number alone is meaningless ('$200', '80%') - SR users hear " \
33
+ "aria-valuetext.",
34
+ "Range mode: values must be sorted [low, high]; use min_steps_between_thumbs to keep a " \
35
+ "meaningful gap.",
36
+ "Do not use Slider for precise known-number entry (use Input type=number) or in " \
37
+ "no-JS-required forms (native input type=range).",
38
+ "Debounce on poetry:slider:commit, never on :change (change fires every drag frame).",
39
+ "Never transition the thumb/range position with CSS - geometry must track the pointer."
40
+ ].freeze
41
+
42
+ # One dedicated controller on purpose: slider keys move VALUES on
43
+ # one focused thumb - value-math no focus-moving primitive covers.
44
+ use_stimulus do
45
+ on :root do
46
+ controller :slider do
47
+ register
48
+ value :min, from: :min_number
49
+ value :max, from: :max_number
50
+ value :step, from: :step_number
51
+ value :value, from: :value_numbers
52
+ value :min_steps_between_thumbs
53
+ value :orientation
54
+ value :inverted
55
+ action :pointerdown, on: :pointerdown
56
+ end
57
+ end
58
+ on :track do
59
+ controller(:slider) { target :track }
60
+ end
61
+ on :range do
62
+ controller(:slider) { target :range }
63
+ end
64
+ on :thumb do
65
+ controller :slider do
66
+ target :thumb
67
+ action :keydown, on: :keydown
68
+ end
69
+ end
70
+ # The form bridge: hidden inputs submit the server value even
71
+ # when disabled.
72
+ on :input do
73
+ controller(:slider) { target :input }
74
+ end
75
+ end
76
+
77
+ option :name, :string, required: true,
78
+ doc: "Form name; single thumb -> name; range -> name + \"[]\" per input (the Rails " \
79
+ "array-param convention)."
80
+ option :value, ActiveModel::Type::Value.new, doc: "Single-thumb value. ArgumentError if given with values:."
81
+ option :values, ActiveModel::Type::Value.new,
82
+ doc: "Range mode: [low, high] -> two thumbs, sorted. ArgumentError if given with value:, unsorted, or " \
83
+ "length != 2."
84
+ option :min, :float, default: 0.0, doc: "The track's lower bound."
85
+ option :max, :float, default: 100.0, doc: "The track's upper bound; must exceed min:."
86
+ option :step, :float, default: 1.0,
87
+ doc: "Snap increment; decimal steps supported (precision-aware rounding lives in the " \
88
+ "controller's math core)."
89
+ option :min_steps_between_thumbs, :integer, default: 0,
90
+ doc: "Range-mode minimum gap in STEPS: high - low >= n*step; " \
91
+ "thumbs can never cross."
92
+ option :orientation, :symbol, default: :horizontal, doc: "The track axis."
93
+ option :inverted, :boolean, default: false,
94
+ doc: "Flips the value direction along the axis; composes with RTL (both = ltr " \
95
+ "math)."
96
+ option :disabled, :boolean, default: false,
97
+ doc: "Renders the control inert; the hidden inputs still submit the server value."
98
+ option :label, ActiveModel::Type::Value.new,
99
+ doc: "Per-thumb accessible names -> aria-label; range REQUIRES two. Alternatively labelled_by " \
100
+ "(external wiring). Enforced."
101
+ option :labelled_by, :string,
102
+ doc: "aria-labelledby for the thumb(s) - the Field-wrapped single slider derives its name from the " \
103
+ "field label this way."
104
+ option :value_text, ActiveModel::Type::Value.new,
105
+ doc: "aria-valuetext formatter: a proc (v -> \"$200\") or an i18n key " \
106
+ "with %{value}; optional - falls back to the bare number." # rubocop:disable Style/FormatStringToken
107
+ option :described_by, :string, doc: "Field hint/error wiring -> aria-describedby on EACH thumb."
108
+
109
+ validates :orientation, inclusion: { in: ORIENTATIONS }
110
+
111
+ part "slider", "Root - the controller, the geometry vars, and pointer capture ride here",
112
+ states: {
113
+ "data-orientation" => { condition: "always - the axis",
114
+ values: ORIENTATIONS.map(&:to_s) },
115
+ "data-disabled" => "disabled: is set (the control is inert; the hidden inputs " \
116
+ "still submit)",
117
+ "data-dragging" => "a pointer drag is in flight (the controller sets it for the " \
118
+ "gesture; never rendered server-side)"
119
+ },
120
+ vars: {
121
+ "--slider-start" => "the filled range's start edge as a percentage - " \
122
+ "server-rendered (no first-paint jump), rewritten by the " \
123
+ "controller on every move",
124
+ "--slider-end" => "the filled range's end edge as a percentage (the single-thumb " \
125
+ "value rides here)"
126
+ }
127
+ part "slider-track", "The full-length rail the range paints over",
128
+ states: {
129
+ "data-orientation" => { condition: "always - mirrors the root",
130
+ values: ORIENTATIONS.map(&:to_s) }
131
+ }
132
+ part "slider-range", "The filled span between --slider-start and --slider-end",
133
+ states: {
134
+ "data-orientation" => { condition: "always - mirrors the root",
135
+ values: ORIENTATIONS.map(&:to_s) }
136
+ }
137
+ part "slider-anchor", "Absolutely positioned thumb wrapper (one per thumb, with its " \
138
+ "hidden input alongside) seated on the geometry vars",
139
+ states: {
140
+ "data-orientation" => { condition: "always - mirrors the root",
141
+ values: ORIENTATIONS.map(&:to_s) }
142
+ }
143
+ part "slider-thumb", "The role=slider handle - its own Tab stop, carrying the " \
144
+ "aria-value* surface (bounds neighbor-clamped in range mode)",
145
+ states: {
146
+ "data-orientation" => { condition: "always - mirrors the root",
147
+ values: ORIENTATIONS.map(&:to_s) },
148
+ "data-disabled" => "disabled: is set (tabindex drops to -1)",
149
+ "data-dragging" => "this thumb is the one being dragged (the controller pairs it " \
150
+ "with the root's)"
151
+ }
152
+
153
+ # @api private
154
+ def initialize(attributes = {})
155
+ if attributes.values_at(:value, "value").any? && attributes.values_at(:values, "values").any?
156
+ raise ArgumentError, "value: is the single-thumb API and values: the range API - pass one"
157
+ end
158
+
159
+ super
160
+
161
+ validate_numbers!
162
+ validate_labels!
163
+ end
164
+
165
+ # Always an array internally: [v] single, [low, high] range.
166
+ # @api private
167
+ def thumb_values
168
+ @thumb_values ||= if values.present?
169
+ Array(values).map { |item| numeric(item, "values") }
170
+ else
171
+ # No value at all -> ONE thumb at min.
172
+ [numeric(value.presence || min, "value")]
173
+ end
174
+ end
175
+
176
+ # @api private
177
+ def range?
178
+ thumb_values.length > 1
179
+ end
180
+
181
+ # @api private
182
+ def input_name
183
+ range? ? "#{name}[]" : name
184
+ end
185
+
186
+ # @api private
187
+ def control_id
188
+ @control_id ||= poetry_instance_id("poetry-slider")
189
+ end
190
+
191
+ # @api private
192
+ def thumb_id(index)
193
+ "#{control_id}-thumb-#{index}"
194
+ end
195
+
196
+ # The thumb's EFFECTIVE bounds (the high thumb's min is the low
197
+ # thumb's value + the gap) - server-rendered, then rewritten by
198
+ # the controller on every neighbor move.
199
+ # @api private
200
+ def thumb_min(index)
201
+ index.zero? ? number(min) : number(thumb_values[index - 1] + gap)
202
+ end
203
+
204
+ # @api private
205
+ def thumb_max(index)
206
+ index == thumb_values.length - 1 ? number(max) : number(thumb_values[index + 1] - gap)
207
+ end
208
+
209
+ # @api private
210
+ def thumb_label(index)
211
+ Array(label)[index]
212
+ end
213
+
214
+ # @api private
215
+ def thumb_text(index)
216
+ return unless value_text.present?
217
+
218
+ value = number(thumb_values[index])
219
+ value_text.respond_to?(:call) ? value_text.call(value) : I18n.t(value_text, value: value)
220
+ end
221
+
222
+ # @api private
223
+ def root_attributes
224
+ attrs = {
225
+ "data-slot" => "slider", "id" => control_id,
226
+ "data-orientation" => orientation, "style" => geometry_style
227
+ }
228
+ attrs["data-disabled"] = "" if disabled
229
+ html_attributes.merge_if_not_set(
230
+ attrs.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
231
+ )
232
+ end
233
+
234
+ # @api private
235
+ def track_attributes
236
+ { class: css(:track), "data-slot" => "slider-track", "data-orientation" => orientation }
237
+ .merge(stimulus_attributes_for(:track))
238
+ end
239
+
240
+ # @api private
241
+ def range_attributes
242
+ { class: css(:range), "data-slot" => "slider-range", "data-orientation" => orientation }
243
+ .merge(stimulus_attributes_for(:range))
244
+ end
245
+
246
+ # @api private
247
+ def anchor_attributes(index)
248
+ anchor = if range? && index.zero? then :anchor_start
249
+ elsif index == thumb_values.length - 1 then :anchor_end
250
+ else :anchor_mid
251
+ end
252
+ attrs = { class: classnames(css(:anchor), css(anchor)), "data-slot" => "slider-anchor",
253
+ "data-orientation" => orientation }
254
+ attrs[:style] = "--slider-mid: #{number(percent(thumb_values[index]).to_f)}%;" if anchor == :anchor_mid
255
+ attrs
256
+ end
257
+
258
+ # @api private
259
+ def thumb_attributes(index)
260
+ attrs = {
261
+ role: "slider", id: thumb_id(index), class: css(:thumb),
262
+ tabindex: disabled ? "-1" : "0",
263
+ "data-slot" => "slider-thumb", "data-orientation" => orientation,
264
+ "aria-valuemin" => thumb_min(index), "aria-valuemax" => thumb_max(index),
265
+ "aria-valuenow" => number(thumb_values[index]),
266
+ "aria-orientation" => orientation
267
+ }
268
+ attrs["aria-valuetext"] = thumb_text(index) if value_text.present?
269
+ attrs["aria-label"] = thumb_label(index) if thumb_label(index).present?
270
+ attrs["aria-labelledby"] = labelled_by if labelled_by.present?
271
+ attrs["aria-describedby"] = described_by if described_by.present?
272
+ attrs["data-disabled"] = "" if disabled
273
+ attrs.merge(stimulus_attributes_for(:thumb))
274
+ end
275
+
276
+ # The form bridge: hidden inputs submit the server value even when
277
+ # disabled (a slider always HAS a value; the control is inert, the
278
+ # datum is not) - contrast native disabled fields.
279
+ # @api private
280
+ def input_attributes(index)
281
+ { type: "hidden", name: input_name, value: number(thumb_values[index]) }
282
+ .merge(stimulus_attributes_for(:input))
283
+ end
284
+
285
+ # Trailing-zero-free rendering: 50.0 -> "50", 0.5 -> "0.5" (aria
286
+ # announcements and params never carry float noise).
287
+ # @api private
288
+ def number(value)
289
+ value == value.to_i ? value.to_i : value
290
+ end
291
+
292
+ private
293
+
294
+ def gap
295
+ min_steps_between_thumbs * step
296
+ end
297
+
298
+ def percent(value)
299
+ span = max - min
300
+ return 0 if span <= 0
301
+
302
+ ((value - min) / span * 100).round(4)
303
+ end
304
+
305
+ def geometry_style
306
+ start = range? ? percent(thumb_values.first) : 0
307
+ "--slider-start: #{number(start.to_f)}%; --slider-end: #{number(percent(thumb_values.last).to_f)}%;"
308
+ end
309
+
310
+ def numeric(value, option_name)
311
+ Float(value)
312
+ rescue ArgumentError, TypeError
313
+ raise ArgumentError, "Slider #{option_name}: must be numeric - got #{value.inspect}"
314
+ end
315
+
316
+ def validate_numbers!
317
+ raise ArgumentError, "Slider max: must be greater than min:" unless max > min
318
+ raise ArgumentError, "Slider step: must be positive" unless step.positive?
319
+
320
+ if values.present?
321
+ list = thumb_values
322
+ raise ArgumentError, "Slider values: takes two or more thumbs" unless list.length >= 2
323
+ raise ArgumentError, "Slider values: must be sorted ascending" unless list.each_cons(2).all? do |a, b|
324
+ a <= b
325
+ end
326
+ end
327
+ return if thumb_values.all? { |item| item.between?(min, max) }
328
+
329
+ raise ArgumentError, "Slider value(s) #{thumb_values.inspect} outside #{number(min)}..#{number(max)}"
330
+ end
331
+
332
+ # Per-thumb accessible names, ENFORCED: a range announcing
333
+ # "slider, 200" twice is useless to AT.
334
+ def validate_labels!
335
+ if range?
336
+ labels = Array(label)
337
+ return if labels.length == thumb_values.length && labels.all?(&:present?)
338
+
339
+ raise ArgumentError, "a multi-thumb Slider requires one distinct name per thumb - " \
340
+ "label: ['Minimum price', 'Maximum price']"
341
+ else
342
+ return if Array(label).first.present? || labelled_by.present?
343
+
344
+ raise ArgumentError, "Slider requires an accessible name - label: (aria-label) or " \
345
+ "labelled_by: (aria-labelledby)"
346
+ end
347
+ end
348
+
349
+ def min_number = number(min)
350
+ def max_number = number(max)
351
+ def step_number = number(step)
352
+ def value_numbers = thumb_values.map { |item| number(item) }
353
+
354
+ private :thumb_values, :range?, :input_name, :control_id, :thumb_id, :thumb_min, :thumb_max, :thumb_label
355
+ private :thumb_text, :root_attributes, :track_attributes, :range_attributes, :anchor_attributes
356
+ private :thumb_attributes, :input_attributes, :number
357
+ end
358
+ end
359
+ end
360
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Slider
6
+ # The Slider preview matrix: modes (single, range, range+gap) x
7
+ # orientation x states, the slider-demo port, and the edge
8
+ # geometries (pinned at min/max, decimal step, inverted).
9
+ class Preview < Poetry::Core::Preview::Base
10
+ # @!group Modes
11
+
12
+ # The slider-demo port: defaultValue [50], max 100, step 1,
13
+ # constrained width.
14
+ def default
15
+ render_component(name: "progress", value: 50, label: "Progress", class: "w-[60%]")
16
+ end
17
+
18
+ # Range mode (field-slider parity): two thumbs, name[] array
19
+ # params, TWO distinct thumb names (enforced).
20
+ def range
21
+ render_component(name: "price_range", values: [200, 800], min: 0, max: 1000, step: 10,
22
+ label: ["Minimum price", "Maximum price"],
23
+ value_text: ->(value) { "$#{value}" })
24
+ end
25
+
26
+ # min_steps_between_thumbs: the thumbs can never close under
27
+ # 10 steps - Home/End clamp at the neighbor's gap.
28
+ def range_with_gap
29
+ render_component(name: "hours", values: [9, 17], min: 0, max: 24,
30
+ min_steps_between_thumbs: 4,
31
+ label: %w[Start End])
32
+ end
33
+
34
+ # @!endgroup
35
+
36
+ # @!group Geometry
37
+
38
+ # Vertical grows BOTTOM-up (APG); ArrowUp still increments.
39
+ def vertical
40
+ render_component(name: "volume", value: 65, orientation: :vertical, label: "Volume",
41
+ value_text: ->(value) { "#{value}%" }, class: "h-48")
42
+ end
43
+
44
+ # inverted: the value direction flips along the axis (increment
45
+ # keys decrement); composes with RTL (both = ltr math).
46
+ def inverted
47
+ render_component(name: "depth", value: 30, inverted: true, label: "Depth")
48
+ end
49
+
50
+ # Decimal step: the controller's precision-aware rounding keeps
51
+ # 0.1 grids exact (0.3, never 0.30000000000000004).
52
+ def decimal_step
53
+ render_component(name: "opacity", value: 0.5, min: 0, max: 1, step: 0.1, label: "Opacity")
54
+ end
55
+
56
+ # Pinned at the edges: range renders 0%/100% deterministically.
57
+ def at_the_edges
58
+ render_component(name: "bounds", values: [0, 100], label: %w[Low High])
59
+ end
60
+
61
+ # @!endgroup
62
+
63
+ # @!group States
64
+
65
+ def disabled
66
+ render_component(name: "volume", value: 40, disabled: true, label: "Volume")
67
+ end
68
+
69
+ # @!endgroup
70
+ end
71
+ end
72
+ end
73
+ end