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,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Exclusive or multi-select toggle sets.
6
+ module ToggleGroup
7
+ # A set of Toggle-styled buttons under one value: type: :single
8
+ # keeps at most one pressed (re-pressing deselects to empty),
9
+ # :multiple toggles each item independently. The group is one Tab
10
+ # stop - arrow keys move focus between items WITHOUT selecting, so
11
+ # browsing options never fires effects; Space/Enter press the
12
+ # focused item. Single groups render role=radiogroup with
13
+ # aria-checked items; multiple groups render role=toolbar with
14
+ # aria-pressed items.
15
+ #
16
+ # A ToggleGroup is UI state, not form data: a single-select that
17
+ # must submit is a RadioGroup, a multi-select that must submit is a
18
+ # checkbox group. spacing: 0 renders the classic segmented control.
19
+ #
20
+ # @example A text-alignment switcher
21
+ # render Poetry::Ui::ToggleGroup::Component.new(value: "left", label: "Text alignment") do |group|
22
+ # group.with_item(value: "left", label: "Align left") { icon(:"align-left") }
23
+ # group.with_item(value: "center", label: "Align center") { icon(:"align-center") }
24
+ # end
25
+ class Component < Poetry::Core::Component
26
+ # The closed vocabulary for the type axis.
27
+ TYPES = %i[single multiple].freeze
28
+ # The closed vocabulary for the orientation axis.
29
+ ORIENTATIONS = %i[horizontal vertical].freeze
30
+
31
+ # Projected into the registry, llms.txt, and the agent surface.
32
+ AGENT_RULES = [
33
+ "Use poetry_toggle_group - never hand-assemble Toggles with your own exclusivity logic.",
34
+ "ToggleGroup is UI state, NOT form data: submitting single-select -> RadioGroup; submitting " \
35
+ "multi-select -> Checkbox group. No name: exists; don't route around it.",
36
+ "Every item MUST have a unique value: (ArgumentError on duplicates); icon-only items MUST " \
37
+ "pass label: (state-invariant).",
38
+ "Name the group via label: - a nameless radiogroup/toolbar fails the audit.",
39
+ "Exclusive view/mode switching that shows PANELS is Tabs (aria-selected + tabpanels), not a " \
40
+ "single ToggleGroup.",
41
+ "Never mix vocabularies: single items carry aria-checked, multiple carry aria-pressed - the " \
42
+ "controller enforces it; agents patching DOM must too.",
43
+ "single deselects to empty by re-press - if your UI needs always-one-selected, " \
44
+ "handle the empty change in the host."
45
+ ].freeze
46
+
47
+ # The same facts the before_render raise enforces, stated statically:
48
+ # poetry check flags the omission without rendering.
49
+ REQUIRED_SLOTS = { item: "at least one item" }.freeze
50
+
51
+ renders_many :items,
52
+ doc: "Declares one item: value: (unique - duplicates raise), label: (required when icon-only), " \
53
+ "disabled:; the block is the content. Pressed state comes from value:/values:.",
54
+ renders: lambda { |value:, label: nil, disabled: false, **options, &block|
55
+ item_value = register_item_value!(value)
56
+ content = capture(&block)
57
+ ensure_item_name!(content, label, item_value)
58
+ on = pressed_values.include?(item_value)
59
+ item_disabled = disabled || self.disabled
60
+
61
+ attrs = {
62
+ type: "button", class: item_classes(options.delete(:class)),
63
+ "data-slot" => "toggle-group-item", "data-poetry-collection-item" => "",
64
+ "data-value" => item_value,
65
+ "data-variant" => variant, "data-size" => size, "data-spacing" => spacing
66
+ }
67
+ # Pressed is a bare presence attribute: data-pressed when on,
68
+ # absent when off (never data-pressed=false).
69
+ attrs["data-pressed"] = "" if on
70
+ # The role/vocabulary split: single wears the radio vocabulary
71
+ # (aria-checked), never aria-pressed.
72
+ if single?
73
+ attrs["role"] = "radio"
74
+ attrs["aria-checked"] = on.to_s
75
+ else
76
+ attrs["aria-pressed"] = on.to_s
77
+ end
78
+ if item_disabled
79
+ attrs[:disabled] = true
80
+ attrs["data-disabled"] = "" # the roving-focus collection filter
81
+ end
82
+ attrs["aria-label"] = label if label.present?
83
+ attrs.merge!(stimulus_attributes_for(:item))
84
+
85
+ content_tag(:button, content, Poetry::Core::HTML::Attributes.merged(attrs, options))
86
+ }
87
+
88
+ use_stimulus do
89
+ on :root do
90
+ controller :toggle_group do
91
+ register
92
+ value :type
93
+ end
94
+ controller :roving_focus do
95
+ register
96
+ # DEFAULT tabindex-managing mode (contrast Accordion's
97
+ # manage_tabindex: false).
98
+ value :orientation
99
+ value :loop, true
100
+ action :keydown, on: :keydown
101
+ end
102
+ end
103
+ on :item do
104
+ controller(:toggle_group) { action :toggle, on: :click }
105
+ end
106
+ end
107
+
108
+ style :variant, default: :default, required: true, variants: Toggle::Component::VARIANTS,
109
+ doc: "The shared Toggle variant axis, cascaded from the root to every item (root wins)."
110
+ style :size, default: :default, required: true, variants: Toggle::Component::SIZES,
111
+ doc: "The shared Toggle size axis, cascaded from the root to every item (root wins)."
112
+
113
+ option :type, :symbol, default: :single,
114
+ doc: ":single keeps at most one item pressed; :multiple toggles items independently."
115
+ option :value, :string, doc: "single: the pressed item's value. ArgumentError with :multiple."
116
+ option :values, :list, default: -> { [] },
117
+ doc: "multiple: the pressed items' values. ArgumentError with :single."
118
+ option :spacing, :integer, default: 2,
119
+ doc: "0 = the classic segmented control (joined corners, collapsed outline " \
120
+ "borders); >0 = free-standing items separated by that gap step."
121
+ option :orientation, :symbol, default: :horizontal,
122
+ doc: "The roving axis; :vertical stacks the items and flips the arrow keys."
123
+ option :disabled, :boolean, default: false, doc: "Disables every item in the group."
124
+ option :label, :string,
125
+ doc: "The group's accessible name (aria-label) - a nameless radiogroup/toolbar logs a lint warning."
126
+
127
+ validates :orientation, inclusion: { in: ORIENTATIONS }
128
+
129
+ part "toggle-group", "The role=radiogroup (single) / role=toolbar (multiple) root - the " \
130
+ "value-set machine and roving focus ride here; the axes cascade to items",
131
+ states: {
132
+ "data-variant" => { condition: "the shared Toggle variant (root wins)",
133
+ values: Toggle::Component::VARIANTS.map(&:to_s) },
134
+ "data-size" => { condition: "the shared Toggle size (root wins)",
135
+ values: Toggle::Component::SIZES.map(&:to_s) },
136
+ "data-spacing" => "the gap step - 0 is the segmented chain (joined corners), >0 free-standing",
137
+ "data-orientation" => { condition: "the roving axis", values: ORIENTATIONS.map(&:to_s) },
138
+ "data-disabled" => "the whole group is disabled (disables every item)"
139
+ },
140
+ vars: {
141
+ "--gap" => "the item gap, set inline from spacing: - the root's gap utility consumes it"
142
+ }
143
+ part "toggle-group-item", "One dumb <button> under the group machine - Toggle-styled, no " \
144
+ "per-item controller",
145
+ states: {
146
+ "data-pressed" => "pressed (bare presence boolean - absent when off; the controller " \
147
+ "rederives the type-correct aria attribute from it)",
148
+ "data-disabled" => "the item (or the whole group) is disabled - the roving-focus " \
149
+ "collection filter",
150
+ "data-value" => "the item's key in the value set (always present, unique)",
151
+ "data-variant" => { condition: "cascaded from the root",
152
+ values: Toggle::Component::VARIANTS.map(&:to_s) },
153
+ "data-size" => { condition: "cascaded from the root",
154
+ values: Toggle::Component::SIZES.map(&:to_s) },
155
+ "data-spacing" => "cascaded from the root - keys the segmented corner/border chain"
156
+ }
157
+
158
+ # The wrong value pair for the type is unrepresentable, not ignored.
159
+ # @api private
160
+ def initialize(attributes = {})
161
+ type = (attributes[:type] || attributes["type"] || :single).to_sym
162
+ raise ArgumentError, "ToggleGroup type must be :single or :multiple" unless TYPES.include?(type)
163
+
164
+ if type == :single && attributes.values_at(:values, "values").any?
165
+ raise ArgumentError, "values: is the :multiple API - single takes value: (one pressed item)"
166
+ end
167
+ if type == :multiple && attributes.values_at(:value, "value").any?
168
+ raise ArgumentError, "value: is the :single API - multiple takes values: (an array)"
169
+ end
170
+
171
+ super
172
+ end
173
+
174
+ # @api private
175
+ def before_render
176
+ raise ArgumentError, "ToggleGroup requires at least one with_item" unless items?
177
+
178
+ return if named?
179
+
180
+ # Lint-level warning, not an error: a radiogroup/toolbar SHOULD
181
+ # have an accessible name.
182
+ Rails.logger&.warn(
183
+ "poetry ToggleGroup: nameless #{single? ? "radiogroup" : "toolbar"} - give it label: (or aria-labelledby)"
184
+ )
185
+ end
186
+
187
+ # @api private
188
+ def single?
189
+ type != :multiple
190
+ end
191
+
192
+ # @api private
193
+ def pressed_values
194
+ @pressed_values ||= (single? ? Array(value) : Array(values)).compact.map(&:to_s)
195
+ end
196
+
197
+ # @api private
198
+ def root_attributes
199
+ attrs = {
200
+ "role" => single? ? "radiogroup" : "toolbar",
201
+ "data-slot" => "toggle-group",
202
+ "data-variant" => variant, "data-size" => size, "data-spacing" => spacing,
203
+ "data-orientation" => orientation, "style" => "--gap: #{spacing}"
204
+ }
205
+ attrs["aria-label"] = label if label.present?
206
+ attrs["data-disabled"] = "" if disabled
207
+ html_attributes.merge_if_not_set(
208
+ attrs.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
209
+ )
210
+ end
211
+
212
+ private
213
+
214
+ def register_item_value!(value)
215
+ @item_values ||= Set.new
216
+ item_value = value.to_s
217
+ unless @item_values.add?(item_value)
218
+ raise ArgumentError, "duplicate ToggleGroup item value #{item_value.inspect} - values key the set machine"
219
+ end
220
+
221
+ item_value
222
+ end
223
+
224
+ def ensure_item_name!(content, label, item_value)
225
+ return if label.present? || content.to_s.gsub(/<[^>]+>/, " ").strip.present?
226
+
227
+ raise ArgumentError,
228
+ "icon-only ToggleGroup item #{item_value.inspect} requires label: (the accessible name, " \
229
+ "state-invariant)"
230
+ end
231
+
232
+ def named?
233
+ aria = html_attributes["aria"] || {}
234
+ label.present? ||
235
+ html_attributes["aria-label"].present? || html_attributes["aria-labelledby"].present? ||
236
+ aria["label"].present? || aria["labelledby"].present?
237
+ end
238
+
239
+ # Shared-not-copied: the item's variant/size classes come from
240
+ # Toggle's dictionary (the toggleVariants import graph); this
241
+ # component's dictionary contributes only the item overrides. The
242
+ # merger collapses conflicts exactly as the source's cn() does
243
+ # (px-3 wins, min-w-0 wins).
244
+ def item_classes(extra)
245
+ classnames(Toggle::Style.css(variant: variant, size: size), css(:item), extra)
246
+ end
247
+
248
+ private :single?, :pressed_values, :root_attributes
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module ToggleGroup
6
+ # The ToggleGroup preview matrix: both types x both variants x both
7
+ # spacing modes with mixed item states - the toggle-group-demo corpus
8
+ # plus the segmented picker and the disabled-item edge.
9
+ class Preview < Poetry::Core::Preview::Base
10
+ # @!group Types
11
+
12
+ # The toggle-group-demo port: MULTIPLE + outline, icon items each
13
+ # with a state-invariant aria-label (toolbar semantics).
14
+ def default
15
+ render_component(type: :multiple, variant: :outline, values: %w[bold],
16
+ label: "Text formatting") do |group|
17
+ formatting_items(group)
18
+ end
19
+ end
20
+
21
+ # single = a radio group that can deselect to empty: role=radiogroup,
22
+ # items role=radio + aria-checked (aria-pressed stripped).
23
+ def single
24
+ render_component(type: :single, value: "center", label: "Text alignment") do |group|
25
+ alignment_items(group)
26
+ end
27
+ end
28
+
29
+ # @!endgroup
30
+
31
+ # @!group Variants
32
+
33
+ # The classic segmented control: single + outline + spacing 0
34
+ # (joined corners, collapsed borders, focus ring painting over).
35
+ def segmented
36
+ render_component(type: :single, variant: :outline, value: "list", spacing: 0,
37
+ label: "View") do |group|
38
+ group.with_item(value: "list", label: "List view") { embed(Icon::Component.new(name: :list)) }
39
+ group.with_item(value: "grid", label: "Grid view") { embed(Icon::Component.new(name: :"layout-grid")) }
40
+ end
41
+ end
42
+
43
+ # spacing > 0: free-standing toggles with a gap (each keeps its own
44
+ # rounded corners + shadow).
45
+ def spaced
46
+ render_component(type: :multiple, variant: :outline, spacing: 2, values: %w[bold italic],
47
+ label: "Text formatting") do |group|
48
+ formatting_items(group)
49
+ end
50
+ end
51
+
52
+ # orientation: :vertical - the column layout (data-vertical flips
53
+ # the root; segment chains and radii are orientation-guarded).
54
+ def vertical
55
+ render_component(type: :multiple, orientation: :vertical, spacing: 1,
56
+ values: %w[bold italic], label: "Text formatting") do |group|
57
+ formatting_items(group)
58
+ end
59
+ end
60
+
61
+ # The vertical SEGMENTED control: the orientation-guarded border/
62
+ # radius chain (border-t collapse, first rounded-t / last rounded-b).
63
+ def vertical_segmented
64
+ render_component(type: :single, variant: :outline, orientation: :vertical, spacing: 0,
65
+ value: "list", label: "View") do |group|
66
+ group.with_item(value: "list", label: "List view") { embed(Icon::Component.new(name: :list)) }
67
+ group.with_item(value: "grid", label: "Grid view") { embed(Icon::Component.new(name: :"layout-grid")) }
68
+ end
69
+ end
70
+
71
+ # @!endgroup
72
+
73
+ # @!group Sizes
74
+
75
+ def small
76
+ render_component(type: :multiple, size: :sm, variant: :outline, label: "Text formatting") do |group|
77
+ formatting_items(group)
78
+ end
79
+ end
80
+
81
+ def large
82
+ render_component(type: :multiple, size: :lg, label: "Text formatting") do |group|
83
+ formatting_items(group)
84
+ end
85
+ end
86
+
87
+ # @!endgroup
88
+
89
+ # @!group States
90
+
91
+ # A disabled item mid-group: skipped by Tab AND filtered from the
92
+ # roving collection; the segment borders still join.
93
+ def disabled_item
94
+ render_component(type: :single, variant: :outline, value: "left",
95
+ label: "Text alignment") do |group|
96
+ group.with_item(value: "left", label: "Align left") { embed(Icon::Component.new(name: :"text-align-start")) }
97
+ group.with_item(value: "center", label: "Align center", disabled: true) do
98
+ embed(Icon::Component.new(name: :"text-align-center"))
99
+ end
100
+ group.with_item(value: "right", label: "Align right") { embed(Icon::Component.new(name: :"text-align-end")) }
101
+ end
102
+ end
103
+
104
+ def disabled_group
105
+ render_component(type: :multiple, variant: :outline, disabled: true,
106
+ label: "Text formatting") do |group|
107
+ formatting_items(group)
108
+ end
109
+ end
110
+
111
+ # @!endgroup
112
+
113
+ # @!group Composition
114
+
115
+ # Icon + text items (the caller's content block, Toggle-style).
116
+ def with_text
117
+ render_component(type: :single, variant: :outline, value: "preview", label: "Mode") do |group|
118
+ group.with_item(value: "code") { embed(Icon::Component.new(name: :bold)).concat("Code") }
119
+ group.with_item(value: "preview") { "Preview" }
120
+ end
121
+ end
122
+
123
+ # @!endgroup
124
+
125
+ private
126
+
127
+ def formatting_items(group)
128
+ group.with_item(value: "bold", label: "Toggle bold") { embed(Icon::Component.new(name: :bold)) }
129
+ group.with_item(value: "italic", label: "Toggle italic") { embed(Icon::Component.new(name: :italic)) }
130
+ group.with_item(value: "underline", label: "Toggle underline") do
131
+ embed(Icon::Component.new(name: :underline))
132
+ end
133
+ end
134
+
135
+ def alignment_items(group)
136
+ group.with_item(value: "left", label: "Align left") { embed(Icon::Component.new(name: :"text-align-start")) }
137
+ group.with_item(value: "center", label: "Align center") { embed(Icon::Component.new(name: :"text-align-center")) }
138
+ group.with_item(value: "right", label: "Align right") { embed(Icon::Component.new(name: :"text-align-end")) }
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module ToggleGroup
6
+ # Re-expressed through the cn-* theme layer. The gap mechanism
7
+ # (gap-[--spacing(var(--gap))]) and the whole data-[spacing=0]
8
+ # segmented-control chain stay inline - upstream's own split keeps
9
+ # the segmentation machinery in markup; the group radius + the
10
+ # flagged-dead shadow selector ride the theme. Item variant/size
11
+ # classes still come from Toggle::Style (shared, never copied).
12
+ class Style < Poetry::Core::Style
13
+ base "cn-toggle-group group/toggle-group flex w-fit flex-row items-center " \
14
+ "gap-[--spacing(var(--gap))] data-vertical:flex-col data-vertical:items-stretch"
15
+
16
+ # The item overrides over Toggle's classes: w-auto/min-w-0 relax the
17
+ # square-ish standalone shape, focus z-index lets the ring paint
18
+ # OVER adjacent joined segments, and the data-[spacing=0] chain is
19
+ # the segmented control (joined corners + collapsed outline borders).
20
+ # The WHOLE segment-radius cluster (rounded-none + first/last edge
21
+ # radii) is theme-side - split-side rule: an inline
22
+ # rounded-none would beat every theme's edge rounding by layer
23
+ # order (exactly that happened when only the edges moved).
24
+ element :item, "cn-toggle-group-item w-auto min-w-0 shrink-0 focus:z-10 focus-visible:z-10 " \
25
+ "data-[spacing=0]:shadow-none " \
26
+ "group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 " \
27
+ "group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 " \
28
+ "group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l " \
29
+ "group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Single-Tab-stop control strips.
6
+ module Toolbar
7
+ # One Tab stop of grouped controls over a data surface - bulk
8
+ # actions above a table, an editor's control strip. The root wears
9
+ # role=toolbar; the typed slots render real Buttons, Inputs, and
10
+ # Separators, and arrow keys move between them while Tab leaves the
11
+ # whole strip. An Input inside stays safe to edit: horizontal
12
+ # arrows stay with the text caret until it reaches a boundary.
13
+ #
14
+ # label: is required - screen readers announce it on entering the
15
+ # toolbar. Styling is utility-only; restyle via data-slot=toolbar.
16
+ #
17
+ # @example
18
+ # render Poetry::Ui::Toolbar::Component.new(label: "Bulk actions") do |toolbar|
19
+ # toolbar.with_button(variant: :outline) { "Archive" }
20
+ # toolbar.with_separator
21
+ # toolbar.with_input(name: "q", placeholder: "Filter…")
22
+ # end
23
+ class Component < Poetry::Core::Component
24
+ # The roving-focus controller's identifier path.
25
+ ROVING = %i[poetry core roving_focus].freeze
26
+
27
+ # Projected into the registry, llms.txt, and the agent surface.
28
+ AGENT_RULES = [
29
+ "A Toolbar is ONE Tab stop: arrows move between its controls - use it for grouped " \
30
+ "actions over a surface (table bulk actions, editor strips), never as page navigation.",
31
+ "Compose through the typed slots: with_button (a real poetry Button - tag: :a makes it " \
32
+ "a link), with_input (search/filter - the caret keeps its arrow keys), with_separator " \
33
+ "(orientation flips automatically).",
34
+ "label: is the toolbar's accessible name and is required - screen readers announce it " \
35
+ "on entry.",
36
+ "A ToggleGroup composed inside keeps its own arrow navigation (its items rove locally); " \
37
+ "place it between separators so the seam reads as a group."
38
+ ].freeze
39
+
40
+ # The same fact the before_render raise enforces, stated
41
+ # statically: poetry check flags the omission without rendering.
42
+ REQUIRED_SLOTS = { button: "at least one control (with_button / with_input)" }.freeze
43
+
44
+ renders_many :items,
45
+ doc: "The control slots: with_button (a real Button - tag: :a makes it a link), with_input " \
46
+ "(search/filter), with_separator (its orientation flips automatically).",
47
+ types: {
48
+ button: {
49
+ renders: ->(**options) { Poetry::Ui::Button::Component.new(**item_options(options)) },
50
+ as: :button
51
+ },
52
+ input: {
53
+ renders: ->(**options) { Poetry::Ui::Input::Component.new(**item_options(options)) },
54
+ as: :input
55
+ },
56
+ separator: {
57
+ renders: lambda { |**options|
58
+ Poetry::Ui::Separator::Component.new(
59
+ orientation: orientation == :horizontal ? :vertical : :horizontal,
60
+ class: "self-stretch", **options
61
+ )
62
+ },
63
+ as: :separator
64
+ }
65
+ }
66
+
67
+ style :orientation, default: :horizontal, variants: %i[horizontal vertical],
68
+ doc: "The strip's axis; :vertical stacks the controls and flips the arrow keys."
69
+
70
+ option :label, :string, required: true, doc: "The toolbar's accessible name. Required."
71
+ option :loop, :boolean, default: true, doc: "Whether arrow navigation wraps at the ends."
72
+
73
+ part "toolbar", "The role=toolbar root - one Tab stop; arrow keys rove across the " \
74
+ "slotted controls (roving-focus, with the caret guard protecting inputs)",
75
+ states: {
76
+ "data-orientation" => { condition: "always - which arrows rove",
77
+ values: %w[horizontal vertical] }
78
+ }
79
+
80
+ # @api private
81
+ def before_render
82
+ raise ArgumentError, "Toolbar requires at least one control slot" unless items?
83
+ end
84
+
85
+ # @api private
86
+ def call
87
+ content_tag(:div, safe_join(items.map(&:to_s)), **root_attributes.to_attributes)
88
+ end
89
+
90
+ # @api private
91
+ def root_attributes
92
+ html_attributes.merge_if_not_set(
93
+ {
94
+ "role" => "toolbar", "aria-label" => label, "aria-orientation" => orientation,
95
+ "data-slot" => "toolbar", "data-orientation" => orientation
96
+ }.merge(component_data_attributes).merge(roving_attributes)
97
+ )
98
+ end
99
+
100
+ private
101
+
102
+ # Every slotted control is a roving collection item; a disabled one
103
+ # is filtered from the collection (the tabs idiom - roving reads
104
+ # data-disabled, the native attribute alone is invisible to it).
105
+ def item_options(options)
106
+ extra = { "data-poetry-collection-item" => "" }
107
+ extra["data-disabled"] = "" if options[:disabled]
108
+ options.merge(extra)
109
+ end
110
+
111
+ def roving_attributes
112
+ attrs = Poetry::Core::HTML::Attributes.new
113
+ roving = Poetry::Core::Stimulus::Builder.new(ROVING, attrs)
114
+ roving.register_controller
115
+ roving.with_value(:orientation, orientation)
116
+ roving.with_value(:loop, loop)
117
+ roving.with_action(:keydown, on: :keydown)
118
+ attrs.to_attributes
119
+ end
120
+
121
+ private :root_attributes
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Toolbar
6
+ # The Toolbar preview: a formatting strip, the table bulk-action
7
+ # row with a filter input, and the vertical orientation.
8
+ class Preview < Poetry::Core::Preview::Base
9
+ def default
10
+ render_component(label: "Text formatting") do |toolbar|
11
+ toolbar.with_button(variant: :ghost, size: :sm) { "Bold" }
12
+ toolbar.with_button(variant: :ghost, size: :sm) { "Italic" }
13
+ toolbar.with_separator
14
+ toolbar.with_button(variant: :ghost, size: :sm) { "Link" }
15
+ end
16
+ end
17
+
18
+ # The table bulk-action strip: actions, a seam, then the filter
19
+ # input - the roving caret guard keeps the input's arrow keys.
20
+ def bulk_actions_with_filter
21
+ render_component(label: "Invoice actions") do |toolbar|
22
+ toolbar.with_button(variant: :outline, size: :sm) { "Export" }
23
+ toolbar.with_button(variant: :outline, size: :sm, disabled: true) { "Archive" }
24
+ toolbar.with_separator
25
+ toolbar.with_input(type: "search", placeholder: "Filter invoices…",
26
+ "aria-label": "Filter invoices")
27
+ end
28
+ end
29
+
30
+ def vertical
31
+ render_component(label: "Canvas tools", orientation: :vertical) do |toolbar|
32
+ toolbar.with_button(variant: :ghost, size: :sm) { "Select" }
33
+ toolbar.with_button(variant: :ghost, size: :sm) { "Draw" }
34
+ toolbar.with_separator
35
+ toolbar.with_button(variant: :ghost, size: :sm) { "Erase" }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Toolbar
6
+ # Utility-only (the Separator/Spinner rule): there is no upstream
7
+ # look to be faithful to - the strip is a structural gap row;
8
+ # data-slot=toolbar is the restyle seam for any theme that wants
9
+ # chrome (border, background) on it.
10
+ class Style < Poetry::Core::Style
11
+ base "flex w-fit items-center gap-1"
12
+
13
+ variant :orientation, {
14
+ horizontal: "flex-row",
15
+ vertical: "flex-col items-stretch"
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
2
+ <%= trigger %>
3
+ <%= content_tag(:div, **Poetry::Core::HTML::Attributes.new(content_attributes).to_attributes) do %>
4
+ <% if label.present? %>
5
+ <%# label: substitutes the ANNOUNCED body - rich children stay visual
6
+ (one content node: visual wrapper hidden from AT, sr-only text
7
+ carries the announcement). %>
8
+ <span aria-hidden="true"><%= content %></span>
9
+ <span class="sr-only"><%= label %></span>
10
+ <% else %>
11
+ <%= content %>
12
+ <% end %>
13
+ <%# invisible until popper stamps data-side: an unpositioned arrow
14
+ (pinned/server-open, popper not yet run) is a stray nub in-flow. %>
15
+ <%# no whitespace inside the wrapper: a stray text node makes a line
16
+ box that stacks above the block diamond and inflates the measured
17
+ arrow height (28px for a 10px diamond - the bottom-side gap bug). %>
18
+ <span data-slot="tooltip-arrow" aria-hidden="true" class="invisible data-[side]:visible inline-block leading-none" <%= tag.attributes(**stimulus_attributes_for(:arrow)) %>><span class="<%= css(:arrow) %>"></span></span>
19
+ <% end %>
20
+ <% end %>