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,493 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # A desktop-style menu bar of drop-down menus.
6
+ module Menubar
7
+ # Shared vocabularies, declared once at module level so the root
8
+ # Component and the nested menu-level classes read the same lists.
9
+ DIRS = %i[ltr rtl].freeze
10
+
11
+ # The shared menus-family helpers plus this family's fork: Menubar
12
+ # themes its checkbox and radio indicators separately (a per-kind
13
+ # class rides along).
14
+ #
15
+ # @api private
16
+ module Helpers
17
+ include Poetry::Ui::Menus::Helpers
18
+
19
+ private
20
+
21
+ def indicator_extra_class(kind)
22
+ "cn-menubar-#{kind}-item-indicator"
23
+ end
24
+ end
25
+
26
+ # The shared menus-family item slots wearing this family's identity -
27
+ # SLOT_BUILDERS (reopened at the bottom, once the classes exist) keeps
28
+ # the registry recursion on the family classes.
29
+ #
30
+ # @api private
31
+ module ItemSlots
32
+ extend ActiveSupport::Concern
33
+ include Poetry::Ui::Menus::ItemSlots
34
+ include Helpers
35
+ end
36
+
37
+ # A desktop-app command bar: File / Edit / View menus on one
38
+ # role=menubar row. The bar is a single tab stop - arrow keys move
39
+ # between the top-level triggers - and each trigger opens a full
40
+ # drop-down menu (action items, checkbox and radio rows, submenus,
41
+ # separators, shortcut hints). Menus are non-modal, so once one is
42
+ # open, hovering a sibling trigger slides to its menu.
43
+ #
44
+ # label: (the bar's accessible name) and at least one with_menu are
45
+ # required. For site navigation use NavigationMenu; for a single
46
+ # actions menu use DropdownMenu.
47
+ #
48
+ # @example An application command bar
49
+ # render Poetry::Ui::Menubar::Component.new(label: "Application") do |bar|
50
+ # bar.with_menu do |menu|
51
+ # menu.with_trigger { "File" }
52
+ # menu.with_item(shortcut: "⌘N") { "New" }
53
+ # menu.with_separator
54
+ # menu.with_item { "Print..." }
55
+ # end
56
+ # end
57
+ class Component < Poetry::Core::Component
58
+ # Projected into the registry, llms.txt, and the agent surface.
59
+ AGENT_RULES = [
60
+ "Use poetry_menubar for app-chrome command menus ONLY - site navigation is NavigationMenu " \
61
+ "(untrapped), a single actions menu is DropdownMenu.",
62
+ "label: is REQUIRED (the bar's accessible name).",
63
+ "Never put non-menuitem interactive elements directly in the bar (breaks roving focus + APG " \
64
+ "roles) - a Toolbar is the component for mixed controls.",
65
+ "shortcut: is a visual hint ONLY - it does not register a keybinding; wire real shortcuts " \
66
+ "separately.",
67
+ "Do not hand-wire hover-open-from-cold; hover only slides between menus once one is open " \
68
+ "(the gated-hover rule).",
69
+ "In-menu item rules (destructive variant, inset, checkbox/radio) follow the DropdownMenu " \
70
+ "family contract."
71
+ ].freeze
72
+
73
+ renders_many :menus,
74
+ doc: "The top-level menus. Each takes with_trigger (the menu button) plus the family item " \
75
+ "slots (with_item, with_checkbox_item, with_radio_group, with_sub, with_separator, ...); " \
76
+ "value: defaults to the menu's position.",
77
+ renders: ->(**options) { Menu.new(bar: self, dir: dir, **options) }
78
+
79
+ use_stimulus do
80
+ on :root do
81
+ controller :menubar do
82
+ register
83
+ value :value, from: :value_string
84
+ value :loop
85
+ # The coordinator's two event seams: the family menu
86
+ # controller's edge-navigate (cross-menu arrows) and closed
87
+ # (value bookkeeping) - custom poetry:-namespaced events.
88
+ action :slide_adjacent, on: "poetry:menu:edge-navigate"
89
+ action :on_menu_closed, on: "poetry:menu:closed"
90
+ end
91
+ controller :roving_focus do
92
+ register
93
+ # The DELTA vs in-menu roving: horizontal, manageTabindex
94
+ # TRUE (one tab stop for the whole bar).
95
+ value :orientation, :horizontal
96
+ value :manage_tabindex, true
97
+ value :loop
98
+ action :keydown, on: :keydown
99
+ end
100
+ end
101
+ # Anatomy-rendered wiring (the Menu/Sub classes build via the
102
+ # escape hatch; declared here for the contract).
103
+ on :menu_wrapper do
104
+ controller :menu do
105
+ register
106
+ value :open
107
+ value :modal, false
108
+ end
109
+ controller :popper do
110
+ register
111
+ value :side, :bottom
112
+ value :align, :start
113
+ value :side_offset, 8
114
+ value :align_offset, -4
115
+ value :avoid_collisions, true
116
+ end
117
+ end
118
+ on :trigger do
119
+ controller :menubar do
120
+ action :toggle, on: :pointerdown
121
+ action :hover_slide, on: :pointerenter
122
+ action :trigger_keydown, on: :keydown
123
+ end
124
+ controller(:popper) { target :anchor }
125
+ end
126
+ on :content do
127
+ controller(:popper) { target :content }
128
+ end
129
+ on :item do
130
+ controller(:menu) { action :activate, on: :click }
131
+ end
132
+ on :sub_trigger do
133
+ controller :menu do
134
+ action :sub_enter, on: :pointerenter
135
+ action :sub_leave, on: :pointerleave
136
+ action :open_sub, on: :click
137
+ end
138
+ controller(:popper) { target :anchor }
139
+ end
140
+ end
141
+
142
+ option :label, :string, required: true,
143
+ doc: "The bar's accessible name - a page may hold more than one menubar."
144
+ option :loop, :boolean, default: false, doc: "Wraps arrow-key movement past either end of the bar."
145
+ option :value, :string,
146
+ doc: "Server-renders the menu with this value open (values default to \"menu-<position>\")."
147
+ option :dir, :symbol, doc: "The reading direction; :rtl flips arrow-key movement and submenu sides."
148
+
149
+ validates :dir, inclusion: { in: DIRS }, allow_nil: true
150
+
151
+ part "menubar", "The role=menubar bar - one horizontal roving tab stop across the triggers",
152
+ states: {
153
+ "data-open" => "some menu is open (value present; the coordinator flips the pair)",
154
+ "data-closed" => "no menu is open"
155
+ }
156
+ part "menubar-menu", "One logical menu - a display:contents wrapper hosting the trigger + " \
157
+ "content pair's menu and popper controllers"
158
+ part "menubar-trigger", "The top-level menu button - a role=menuitem INSIDE the bar",
159
+ states: {
160
+ "data-value" => "the menu's value - the coordinator's open/close key",
161
+ "data-popup-open" => "its menu is open (written with aria-expanded; absence is the " \
162
+ "closed state)",
163
+ "data-disabled" => "trigger is disabled (written together with the disabled property)"
164
+ }
165
+ part "menubar-content", "The role=menu popup panel - positioning, animation, and the open " \
166
+ "state ride here",
167
+ states: {
168
+ "data-open" => "menu is open (presence flips the pair at runtime)",
169
+ "data-closed" => "menu is closed or animating out (the server-rendered state)",
170
+ "data-side" => { condition: "the placement side (bottom initially; popper re-writes it " \
171
+ "after collision flips)",
172
+ values: %w[top right bottom left] },
173
+ "data-align" => { condition: "the alignment against the trigger (start initially; popper " \
174
+ "re-resolves it)",
175
+ values: %w[start center end] }
176
+ },
177
+ vars: {
178
+ "--transform-origin" => "popper's anchor-facing animation origin",
179
+ "--available-width" => "popper: viewport space left for the panel (post-flip)",
180
+ "--available-height" => "popper: viewport space left for the panel (post-flip)",
181
+ "--anchor-width" => "popper: the trigger's measured width",
182
+ "--anchor-height" => "popper: the trigger's measured height"
183
+ }
184
+ part "menubar-group", "role=group semantic grouping between separators"
185
+ part "menubar-label", "Non-interactive heading for a run of items",
186
+ states: {
187
+ "data-inset" => "indented to align with checkbox/radio item text (inset: true)"
188
+ }
189
+ part "menubar-item", "One role=menuitem action row",
190
+ states: {
191
+ "data-variant" => "default or destructive (the danger treatment)",
192
+ "data-inset" => "indented to align with checkbox/radio item text (inset: true)",
193
+ "data-disabled" => "item is disabled (always written together with aria-disabled)"
194
+ }
195
+ part "menubar-checkbox-item", "A role=menuitemcheckbox toggle row",
196
+ states: {
197
+ "data-checked" => "checked (the controller re-writes the pair with aria-checked on " \
198
+ "activation)",
199
+ "data-unchecked" => "unchecked",
200
+ "data-disabled" => "item is disabled (always written together with aria-disabled)",
201
+ "data-close-on-select" => "per-item override of the menu's close-on-select default " \
202
+ "(\"false\" keeps the menu open)"
203
+ }
204
+ part "menubar-radio-group", "role=group scoping one single-select value",
205
+ states: {
206
+ "data-value" => "the selected radio value (the controller re-writes it on change)"
207
+ }
208
+ part "menubar-radio-item", "A role=menuitemradio row inside a radio group",
209
+ states: {
210
+ "data-checked" => "the selected radio (the controller re-writes the pair with aria-checked)",
211
+ "data-unchecked" => "not selected",
212
+ "data-value" => "the radio's value",
213
+ "data-disabled" => "item is disabled (always written together with aria-disabled)"
214
+ }
215
+ part "menubar-checkbox-item-indicator", "The check glyph slot inside checkbox items - " \
216
+ "aria-hidden; the item's aria-checked/data-checked pair carries state"
217
+ part "menubar-radio-item-indicator", "The circle glyph slot inside radio items - aria-hidden; " \
218
+ "the item's aria-checked/data-checked pair carries state"
219
+ part "menubar-separator", "role=separator rule between groups"
220
+ part "menubar-shortcut", "The trailing keybinding HINT - aria-hidden, never binds the key"
221
+ part "menubar-sub", "A submenu scope - hosts its own popper around the sub trigger/content pair"
222
+ part "menubar-sub-trigger", "The role=menuitem row opening its submenu",
223
+ states: {
224
+ "data-popup-open" => "its submenu is open (written with aria-expanded; absence is the " \
225
+ "closed state)",
226
+ "data-inset" => "indented to align with checkbox/radio item text (inset: true)"
227
+ }
228
+ part "menubar-sub-content", "The nested role=menu panel - its own popper content on the same " \
229
+ "presence machinery",
230
+ states: {
231
+ "data-open" => "submenu is open (presence flips the pair at runtime)",
232
+ "data-closed" => "submenu is closed (the server-rendered state)",
233
+ "data-side" => { condition: "the placement side (right/left by direction; popper resolves " \
234
+ "it at runtime)",
235
+ values: %w[top right bottom left] },
236
+ "data-align" => { condition: "the alignment against the sub-trigger (popper resolves it " \
237
+ "at runtime)",
238
+ values: %w[start center end] }
239
+ },
240
+ vars: {
241
+ "--transform-origin" => "popper's anchor-facing animation origin",
242
+ "--available-width" => "popper: viewport space left for the panel (post-flip)",
243
+ "--available-height" => "popper: viewport space left for the panel (post-flip)",
244
+ "--anchor-width" => "popper: the sub-trigger's measured width",
245
+ "--anchor-height" => "popper: the sub-trigger's measured height"
246
+ }
247
+
248
+ # Enforces the required label and menu slots.
249
+ # @api private
250
+ def before_render
251
+ # The bar's accessible name is not optional - a page may hold
252
+ # more than one menubar.
253
+ raise ArgumentError, "Menubar requires label: (the bar's accessible name)" if label.blank?
254
+ raise ArgumentError, "Menubar requires at least one with_menu" unless menus?
255
+ end
256
+
257
+ # @api private
258
+ def value_string = value.to_s
259
+
260
+ # @api private
261
+ def root_attributes
262
+ root = {
263
+ "data-slot" => "menubar", "role" => "menubar", "aria-label" => label,
264
+ # The bar ROOT keeps the open/closed pair so themes can key
265
+ # on bar-wide state.
266
+ (value.present? ? "data-open" : "data-closed") => ""
267
+ }
268
+ root["dir"] = dir.to_s if dir
269
+ html_attributes.merge_if_not_set(
270
+ root.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
271
+ )
272
+ end
273
+
274
+ # The bar-level tab stop is server-rendered (exactly one tabindex=0
275
+ # before any JS): the open menu's trigger when value: matches,
276
+ # otherwise the first enabled trigger.
277
+ # @api private
278
+ def tab_stop?(menu)
279
+ tab_stop_menu.equal?(menu)
280
+ end
281
+
282
+ # @api private
283
+ def open_menu?(menu_value)
284
+ value.present? && value.to_s == menu_value
285
+ end
286
+
287
+ # Registers a Menu part and hands back its 1-based position (the
288
+ # default value: "menu-<position>").
289
+ # @api private
290
+ def register_menu(menu)
291
+ menu_parts << menu
292
+ menu_parts.size
293
+ end
294
+
295
+ private
296
+
297
+ def menu_parts
298
+ @menu_parts ||= []
299
+ end
300
+
301
+ def tab_stop_menu
302
+ menu_parts.find { |menu| open_menu?(menu.value) && !menu.disabled } ||
303
+ menu_parts.find { |menu| !menu.disabled } || menu_parts.first
304
+ end
305
+
306
+ private :value_string, :root_attributes
307
+ end
308
+
309
+ # One logical menu: the trigger + content pair, hosted on a
310
+ # display:contents wrapper that its behavior wiring rides - erased
311
+ # from the bar's flex layout AND the accessibility tree, so the
312
+ # rendered semantics stay a flat menubar. The nested parts are
313
+ # anatomy, not registry components.
314
+ #
315
+ # @api private
316
+ class Menu < Poetry::Core::Component
317
+ internal_component!
318
+ include ItemSlots
319
+
320
+ attr_reader :value, :disabled
321
+
322
+ renders_one :trigger,
323
+ doc: "The top-level menu button: a real button that is role=menuitem INSIDE role=menubar, wired " \
324
+ "to the bar coordinator (toggle / gated hover-slide / keyboard open).",
325
+ renders: lambda { |**options, &block|
326
+ attrs = {
327
+ "type" => "button", "id" => trigger_id, "data-slot" => "menubar-trigger",
328
+ "role" => "menuitem", "tabindex" => @bar.tab_stop?(self) ? "0" : "-1",
329
+ "data-poetry-collection-item" => "",
330
+ "aria-haspopup" => "menu", "aria-expanded" => open?.to_s, "aria-controls" => content_id,
331
+ "data-value" => value,
332
+ "class" => Style.css(:trigger, class: options.delete(:class))
333
+ }.merge(trigger_stimulus_attributes)
334
+ # Trigger open state: bare data-popup-open while open, NO
335
+ # attribute while closed (absence IS the state).
336
+ attrs["data-popup-open"] = "" if open?
337
+ if disabled
338
+ attrs["disabled"] = true
339
+ attrs["data-disabled"] = ""
340
+ end
341
+ content_tag(:button, Poetry::Core::HTML::Attributes.merged(attrs, options)) { capture(&block) }
342
+ }
343
+
344
+ def initialize(bar:, value: nil, disabled: false, dir: nil, **extra_attributes)
345
+ super(extra_attributes)
346
+ @bar = bar
347
+ @disabled = disabled
348
+ @dir = dir
349
+ position = @bar.register_menu(self)
350
+ @value = (value || "menu-#{position}").to_s
351
+ end
352
+
353
+ def before_render
354
+ raise ArgumentError, "Menubar menu requires with_trigger (the top-level menu button)" unless trigger?
355
+ raise ArgumentError, "Menubar menu requires at least one item" unless items?
356
+ end
357
+
358
+ def call
359
+ content_tag(:div, menu_attributes) { safe_join([trigger, menu_content]) }
360
+ end
361
+
362
+ def open?
363
+ @bar.open_menu?(value)
364
+ end
365
+
366
+ def trigger_id
367
+ "#{instance_id}-trigger"
368
+ end
369
+
370
+ def content_id
371
+ "#{instance_id}-content"
372
+ end
373
+
374
+ private
375
+
376
+ def menu_dir
377
+ @dir
378
+ end
379
+
380
+ def instance_id
381
+ @instance_id ||= poetry_instance_id("poetry-menubar")
382
+ end
383
+
384
+ # The per-menu machinery scope: the family menu controller (modal
385
+ # FALSE - the bar's hover-slide needs sibling triggers pressable,
386
+ # so no body scrim / no trap) + its popper with the menubar
387
+ # positioning overrides (align start / alignOffset -4 / sideOffset 8).
388
+ def menu_attributes
389
+ wiring = stimulus_attributes(:menu, :popper) do |menu, popper|
390
+ menu.register_controller
391
+ menu.with_value(:open, open?)
392
+ menu.with_value(:modal, false)
393
+ popper.register_controller
394
+ popper.with_value(:side, :bottom)
395
+ popper.with_value(:align, :start)
396
+ popper.with_value(:side_offset, 8)
397
+ popper.with_value(:align_offset, -4)
398
+ popper.with_value(:avoid_collisions, true)
399
+ end
400
+ { "data-slot" => "menubar-menu", "class" => Style.css(:menu) }
401
+ .merge(wiring).then { |w| Poetry::Core::HTML::Attributes.merged(w, html_attributes) }
402
+ end
403
+
404
+ def trigger_stimulus_attributes
405
+ stimulus_attributes(:menubar, :popper) do |menubar, popper|
406
+ menubar.with_action(:toggle, on: :pointerdown)
407
+ menubar.with_action(:hover_slide, on: :pointerenter)
408
+ menubar.with_action(:trigger_keydown, on: :keydown)
409
+ popper.with_target(:anchor)
410
+ end
411
+ end
412
+
413
+ # NOT named `content` - that would shadow ViewComponent's own slot
414
+ # content accessor and recurse through the slot machinery.
415
+ def menu_content
416
+ attrs = {
417
+ "id" => content_id, "role" => "menu", "aria-orientation" => "vertical",
418
+ "aria-labelledby" => trigger_id, "tabindex" => "-1",
419
+ "data-slot" => "menubar-content", (open? ? "data-open" : "data-closed") => "",
420
+ # Initial placement, re-resolved live by popper on open.
421
+ "data-side" => "bottom", "data-align" => "start",
422
+ "class" => Style.css(:content)
423
+ }.merge(stimulus_attributes(:popper) { |popper| popper.with_target(:content) })
424
+ attrs["hidden"] = true unless open?
425
+ content_tag(:div, attrs) { safe_join(items.map(&:to_s)) }
426
+ end
427
+ end
428
+
429
+ # role=group semantic grouping between separators - the shared
430
+ # menus-family anatomy wearing this family's identity.
431
+ #
432
+ # @api private
433
+ class Group < Poetry::Ui::Menus::Group
434
+ include ItemSlots
435
+ end
436
+
437
+ # role=group scoping the single-select value for its radio items -
438
+ # the shared menus-family anatomy wearing this family's identity.
439
+ #
440
+ # @api private
441
+ class RadioGroup < Poetry::Ui::Menus::RadioGroup
442
+ include Helpers
443
+ end
444
+
445
+ # A submenu scope (the full item vocabulary again, on its own
446
+ # positioned panel) - the shared menus-family anatomy wearing this
447
+ # family's identity.
448
+ #
449
+ # @api private
450
+ class Sub < Poetry::Ui::Menus::Sub
451
+ include ItemSlots
452
+ end
453
+
454
+ # The builder classes behind lambda-wrapped slots: a lambda
455
+ # hides its return class from introspection, so the owners declare
456
+ # them and the registry walker recurses into each builder's own call
457
+ # surface (with_menu yields a Menu; with_sub a Sub). REQUIRED_SLOTS
458
+ # states the same facts the before_render raises enforce, so static
459
+ # checks flag an omission without rendering.
460
+ class Component
461
+ # Maps slot names to the classes their builder lambdas return.
462
+ SLOT_BUILDERS = { menu: Menu }.freeze
463
+ # Slots the component cannot render without; static checks read this without rendering.
464
+ REQUIRED_SLOTS = { menu: "at least one menu" }.freeze
465
+ end
466
+
467
+ class Menu
468
+ # Slots this menu cannot render without.
469
+ REQUIRED_SLOTS = { trigger: "the top-level menu button", item: "at least one item" }.freeze
470
+ end
471
+
472
+ class Group
473
+ # Slots this group cannot render without.
474
+ REQUIRED_SLOTS = { item: "at least one item" }.freeze
475
+ end
476
+
477
+ class RadioGroup
478
+ # Slots this radio group cannot render without.
479
+ REQUIRED_SLOTS = { radio_item: "at least one radio item" }.freeze
480
+ end
481
+
482
+ class Sub
483
+ # Slots this submenu cannot render without.
484
+ REQUIRED_SLOTS = { trigger: "the sub-menu item", item: "at least one item" }.freeze
485
+ end
486
+
487
+ module ItemSlots
488
+ # Maps nested slot names to the classes their builder lambdas return.
489
+ SLOT_BUILDERS = { sub: Sub, group: Group, radio_group: RadioGroup }.freeze
490
+ end
491
+ end
492
+ end
493
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Menubar
6
+ # The Menubar preview matrix: the menubar-demo.tsx port (File / Edit /
7
+ # View / Profiles - shortcuts, a Share submenu, checkbox view
8
+ # toggles, a radio profile group), plus a disabled trigger, RTL, and
9
+ # the server-rendered open state. Hover-slide and the cross-menu
10
+ # arrows are runtime behavior - the browser pass drives them.
11
+ class Preview < Poetry::Core::Preview::Base
12
+ def default
13
+ render_component(label: "Application menu") do |bar|
14
+ file_menu(bar)
15
+ edit_menu(bar)
16
+ view_menu(bar)
17
+ profiles_menu(bar)
18
+ end
19
+ end
20
+
21
+ # A disabled top-level menu: skipped by roving focus and the
22
+ # cross-menu arrows; the bar keeps exactly one tab stop.
23
+ def disabled_menu
24
+ render_component(label: "Editor menu") do |bar|
25
+ bar.with_menu do |menu|
26
+ menu.with_trigger { "File" }
27
+ menu.with_item { "New" }
28
+ end
29
+ bar.with_menu(disabled: true) do |menu|
30
+ menu.with_trigger { "Team" }
31
+ menu.with_item { "Invite" }
32
+ end
33
+ bar.with_menu do |menu|
34
+ menu.with_trigger { "Help" }
35
+ menu.with_item { "About" }
36
+ end
37
+ end
38
+ end
39
+
40
+ # Bar roving + the cross-menu edge moves flip under RTL; the
41
+ # submenu side flips with them.
42
+ def rtl
43
+ render_component(label: "قائمة التطبيق", dir: :rtl) do |bar|
44
+ bar.with_menu do |menu|
45
+ menu.with_trigger { "ملف" }
46
+ menu.with_item { "جديد" }
47
+ menu.with_sub do |sub|
48
+ sub.with_trigger { "مشاركة" }
49
+ sub.with_item { "بريد إلكتروني" }
50
+ end
51
+ end
52
+ bar.with_menu do |menu|
53
+ menu.with_trigger { "تحرير" }
54
+ menu.with_item { "تراجع" }
55
+ end
56
+ end
57
+ end
58
+
59
+ # Server-rendered open state (controllable-state: value: names the
60
+ # open menu; the coordinator reconciles on connect).
61
+ def open
62
+ render_component(label: "Application menu", value: "file") do |bar|
63
+ bar.with_menu(value: "file") do |menu|
64
+ menu.with_trigger { "File" }
65
+ menu.with_item(shortcut: "⌘T") { "New Tab" }
66
+ menu.with_item { "New Window" }
67
+ end
68
+ bar.with_menu(value: "edit") do |menu|
69
+ menu.with_trigger { "Edit" }
70
+ menu.with_item { "Undo" }
71
+ end
72
+ end
73
+ end
74
+
75
+ # A labelled, grouped menu - the label heading + role=group wrapper.
76
+ def grouped_with_label
77
+ render_component(label: "Grouped") do |bar|
78
+ bar.with_menu do |menu|
79
+ menu.with_trigger { "File" }
80
+ menu.with_label { "Recent" }
81
+ menu.with_group do |group|
82
+ group.with_item { "Report.pdf" }
83
+ group.with_item { "Notes.txt" }
84
+ end
85
+ menu.with_separator
86
+ menu.with_checkbox_item(checked: true, disabled: true) { "Autosave" }
87
+ end
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def file_menu(bar)
94
+ bar.with_menu do |menu|
95
+ menu.with_trigger { "File" }
96
+ menu.with_item(shortcut: "⌘T") { "New Tab" }
97
+ menu.with_item(shortcut: "⌘N") { "New Window" }
98
+ menu.with_item(disabled: true) { "New Incognito Window" }
99
+ menu.with_separator
100
+ menu.with_sub do |sub|
101
+ sub.with_trigger { "Share" }
102
+ sub.with_item { "Email link" }
103
+ sub.with_item { "Messages" }
104
+ sub.with_item { "Notes" }
105
+ end
106
+ menu.with_separator
107
+ menu.with_item(shortcut: "⌘P") { "Print..." }
108
+ end
109
+ end
110
+
111
+ def edit_menu(bar)
112
+ bar.with_menu do |menu|
113
+ menu.with_trigger { "Edit" }
114
+ menu.with_item(shortcut: "⌘Z") { "Undo" }
115
+ menu.with_item(shortcut: "⇧⌘Z") { "Redo" }
116
+ menu.with_separator
117
+ menu.with_sub do |sub|
118
+ sub.with_trigger { "Find" }
119
+ sub.with_item { "Search the web" }
120
+ sub.with_separator
121
+ sub.with_item { "Find..." }
122
+ sub.with_item { "Find Next" }
123
+ sub.with_item { "Find Previous" }
124
+ end
125
+ menu.with_separator
126
+ menu.with_item { "Cut" }
127
+ menu.with_item { "Copy" }
128
+ menu.with_item { "Paste" }
129
+ end
130
+ end
131
+
132
+ def view_menu(bar)
133
+ bar.with_menu do |menu|
134
+ menu.with_trigger { "View" }
135
+ menu.with_checkbox_item(close_on_select: false) { "Always Show Bookmarks Bar" }
136
+ menu.with_checkbox_item(checked: true, close_on_select: false) { "Always Show Full URLs" }
137
+ menu.with_separator
138
+ menu.with_item(inset: true, shortcut: "⌘R") { "Reload" }
139
+ menu.with_item(inset: true, disabled: true, shortcut: "⇧⌘R") { "Force Reload" }
140
+ menu.with_separator
141
+ menu.with_item(inset: true) { "Toggle Fullscreen" }
142
+ menu.with_separator
143
+ menu.with_item(inset: true) { "Hide Sidebar" }
144
+ end
145
+ end
146
+
147
+ def profiles_menu(bar)
148
+ bar.with_menu do |menu|
149
+ menu.with_trigger { "Profiles" }
150
+ menu.with_radio_group(value: "benoit") do |group|
151
+ group.with_radio_item(value: "andy") { "Andy" }
152
+ group.with_radio_item(value: "benoit") { "Benoit" }
153
+ group.with_radio_item(value: "luis") { "Luis" }
154
+ end
155
+ menu.with_separator
156
+ menu.with_item(inset: true) { "Edit..." }
157
+ menu.with_separator
158
+ menu.with_item(inset: true) { "Add Profile..." }
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end