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,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Hover/focus text hints.
6
+ module Tooltip
7
+ # The placement vocabularies - the popper-consumer kit owns them.
8
+ SIDES = Poetry::Ui::PopperConsumer::SIDES
9
+ # The closed vocabulary for the align axis.
10
+ ALIGNS = Poetry::Ui::PopperConsumer::ALIGNS
11
+
12
+ # A hover/focus text hint. The bubble describes its trigger and
13
+ # never receives focus: content is role=tooltip, the trigger's
14
+ # aria-describedby exists only while open (a description pointing
15
+ # at hidden content mis-announces), and no aria-haspopup/expanded
16
+ # is written - a tooltip is a description, not a popup the user
17
+ # operates. Opening is delayed on hover but instant on keyboard
18
+ # focus; only one tooltip is open globally; Esc closes the tooltip
19
+ # first when overlays are stacked; touch never opens one, so
20
+ # essential information must never live only in a tooltip.
21
+ #
22
+ # Content must be plain text, never interactive - use Popover for
23
+ # links or controls. Wrap rows of triggers in one
24
+ # poetry_tooltip_provider so moving along the row skips the delay.
25
+ #
26
+ # @example A described icon button
27
+ # render Poetry::Ui::Tooltip::Component.new do |tooltip|
28
+ # tooltip.with_trigger(variant: :outline, size: :icon, label: "Print") do
29
+ # poetry_icon(name: :printer)
30
+ # end
31
+ # "Print the current page"
32
+ # end
33
+ class Component < Poetry::Core::Component
34
+ include Poetry::Ui::ComposableTrigger
35
+ include Poetry::Ui::PopperConsumer
36
+
37
+ # Projected into the registry, llms.txt, and the agent surface.
38
+ AGENT_RULES = [
39
+ ComposableTrigger::AGENT_RULE,
40
+ "Use poetry_tooltip - never hand-roll title-attribute replacements or hover divs.",
41
+ "Tooltip content is TEXT and never interactive/focusable - links, buttons, or inputs inside " \
42
+ "are a contract violation (use Popover).",
43
+ "Never put essential information only in a tooltip - touch users NEVER see it (no long-press " \
44
+ "path, by design).",
45
+ "The tooltip DESCRIBES; it never names. Icon-only triggers still require label: on the " \
46
+ "composed Button.",
47
+ "Wrap toolbar/button rows in ONE poetry_tooltip_provider so the warm grace makes the row " \
48
+ "feel continuous.",
49
+ "Rich visual content needs label: (the plain-text announcement).",
50
+ "Do not pin tooltips open as onboarding callouts - that is a Popover."
51
+ ].freeze
52
+
53
+ # The same facts the before_render raise enforces, stated statically:
54
+ # poetry check flags the omission without rendering.
55
+ REQUIRED_SLOTS = { trigger: "the described control" }.freeze
56
+
57
+ # The component behind the forwarding slot: with_trigger renders a Button.
58
+ SLOT_RENDERS = { trigger: Button::Component }.freeze
59
+
60
+ renders_one :trigger,
61
+ doc: "The described control - commonly a poetry Button (with_trigger(variant: :outline) { " \
62
+ "\"Hover\" }). The slot owns the state + timing wiring regardless of the composed content. " \
63
+ "NO aria-haspopup/expanded/controls - the tooltip is invisible as a popup; " \
64
+ "aria-describedby is written by the controller on open (and server-rendered only when " \
65
+ "open: true).",
66
+ renders: lambda { |**options, &block|
67
+ wiring = {
68
+ "id" => trigger_id, "data-slot" => "tooltip-trigger"
69
+ }.merge(stimulus_attributes_for(:trigger))
70
+ # Open state is a bare data-popup-open presence attribute -
71
+ # absent while closed (absence IS the state).
72
+ wiring["data-popup-open"] = "" if open
73
+ wiring["aria-describedby"] = content_id if open
74
+ composed_trigger(wiring, options, &block) ||
75
+ Button::Component.new(**wiring, **options, &block)
76
+ }
77
+
78
+ use_stimulus do
79
+ on :root do
80
+ # Full identifier: the bare :tooltip suffix is ambiguous the
81
+ # moment a host also loads poetry-charts (poetry--charts--
82
+ # tooltip joins the manifest catalog).
83
+ controller "poetry--core--tooltip" do
84
+ register
85
+ value :open
86
+ # Inherit-from-provider: unset renders NO attribute - the
87
+ # controller falls back to [data-slot=tooltip-provider].
88
+ value :delay_duration, unless: -> { delay_duration.nil? }
89
+ value :disable_hoverable_content, unless: -> { disable_hoverable_content.nil? }
90
+ end
91
+ controller :popper do
92
+ register
93
+ value :side
94
+ value :align
95
+ value :side_offset
96
+ value :align_offset
97
+ value :avoid_collisions
98
+ end
99
+ end
100
+ # The trigger handlers: pointermove opens (touch excluded, once
101
+ # per hover), pointerdown/click close, focus opens instantly /
102
+ # blur closes.
103
+ on :trigger do
104
+ controller "poetry--core--tooltip" do
105
+ action :pointer_move, on: :pointermove
106
+ action :pointer_leave, on: :pointerleave
107
+ action :pointer_down, on: :pointerdown
108
+ action :click_close, on: :click
109
+ action :focus_open, on: :focus
110
+ action :blur_close, on: :blur
111
+ end
112
+ controller(:popper) { target :anchor }
113
+ end
114
+ on :content do
115
+ controller(:popper) { target :content }
116
+ end
117
+ # The arrow's positioning target.
118
+ on :arrow do
119
+ controller(:popper) { target :arrow }
120
+ end
121
+ end
122
+
123
+ option :open, :boolean, default: false, doc: "Server-renders the tooltip open."
124
+ option :delay_duration, :integer, doc: "The hover-open delay in ms; nil inherits the provider's (default 0)."
125
+ option :disable_hoverable_content, :boolean,
126
+ doc: "When true the bubble closes as the pointer leaves the trigger - it cannot be hovered into; nil " \
127
+ "inherits the provider."
128
+ # Placement defaults: :top with side_offset 0 (the arrow supplies the gap).
129
+ popper_placement_options(side: :top, side_offset: 0)
130
+ option :label, :string,
131
+ doc: "Plain-text announcement override for rich content (the visual children stay; the announced body " \
132
+ "becomes this text)."
133
+ option :content_class, :string, doc: "The bubble's class merge seam (caller classes win on conflicts)."
134
+
135
+ part "tooltip", "Root wrapper around the trigger and the bubble"
136
+ part "tooltip-content", "The role=tooltip bubble - positioning, animation, and the open " \
137
+ "state ride here",
138
+ states: {
139
+ "data-open" => "bubble is open (the controller flips the pair at runtime)",
140
+ "data-closed" => "bubble is closed (the server-rendered state; hidden rides along)",
141
+ "data-instant" => { condition: "the open skipped the delay - warm-grace/" \
142
+ "programmatic or keyboard focus (runtime-only; " \
143
+ "absent on a delayed open)",
144
+ values: %w[delay focus] },
145
+ "data-side" => { condition: "always - the side (initial placement, re-resolved " \
146
+ "live by popper after flip)",
147
+ values: SIDES.map(&:to_s) },
148
+ "data-align" => { condition: "always - the alignment (re-resolved live by popper)",
149
+ values: ALIGNS.map(&:to_s) }
150
+ },
151
+ vars: Poetry::Ui::PopperConsumer.content_vars("bubble")
152
+ part "tooltip-arrow", "The arrow wrapper (aria-hidden) - popper pins it to the bubble's " \
153
+ "anchor-facing edge and rotates it toward the anchor",
154
+ states: {
155
+ "data-side" => { condition: "written by popper alongside the content's - the " \
156
+ "resolved side, for per-side restyling",
157
+ values: SIDES.map(&:to_s) }
158
+ }
159
+
160
+ # @api private
161
+ def before_render
162
+ raise ArgumentError, "Tooltip requires with_trigger (the described control)" unless trigger?
163
+ end
164
+
165
+ # @api private
166
+ def content_attributes
167
+ # A server-pinned open tooltip renders bare data-open; the
168
+ # controller adds data-instant on ITS opens - the reason
169
+ # attribute is runtime-only.
170
+ attrs = {
171
+ "id" => content_id, "role" => "tooltip",
172
+ "data-slot" => "tooltip-content", (open ? "data-open" : "data-closed") => "",
173
+ # Initial placement, re-resolved live by popper on open.
174
+ "data-side" => side, "data-align" => align,
175
+ "class" => css(:content, class: content_class)
176
+ }.merge(stimulus_attributes_for(:content))
177
+ attrs["hidden"] = true unless open
178
+ attrs
179
+ end
180
+
181
+ private :content_attributes
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Tooltip
6
+ # The Tooltip preview matrix: the ported tooltip-demo composition,
7
+ # the provider warm row (one scope, three controls), the label:
8
+ # announcement override, a placement sample, the provider-style opt-back
9
+ # delay, and the server-pinned open state.
10
+ class Preview < Poetry::Core::Preview::Base
11
+ # The tooltip-demo port: outline Button trigger, plain text hint.
12
+ def default
13
+ render_component do |tooltip|
14
+ tooltip.with_trigger(variant: :outline) { "Hover" }
15
+ "Add to library"
16
+ end
17
+ end
18
+
19
+ # ONE provider scope: the first open is delayed, sweeping along
20
+ # the row is instant (the warm grace), cold again 300ms after
21
+ # leaving - wrap toolbar rows exactly like this.
22
+ def provider_row
23
+ render_with_template
24
+ end
25
+
26
+ # Rich visual content with label: - the announced body is the
27
+ # plain-text label; the visual children stay (the kbd recipe).
28
+ def with_label
29
+ render_component(label: "Command S saves the document") do |tooltip|
30
+ tooltip.with_trigger(variant: :outline, label: "Save shortcut") { "Save" }
31
+ "Saves the document <kbd>⌘S</kbd>".html_safe
32
+ end
33
+ end
34
+
35
+ # Server-pinned open (controllable-state; the controller
36
+ # reconciles describedby + the layer on connect).
37
+ def open
38
+ render_component(open: true) do |tooltip|
39
+ tooltip.with_trigger(variant: :outline) { "Pinned" }
40
+ "Server-rendered open"
41
+ end
42
+ end
43
+
44
+ # Placement sample (the ported source defaults side: :top - the trio's
45
+ # odd one out; the arrow follows the resolved side).
46
+ def side_bottom
47
+ render_component(side: :bottom, align: :start) do |tooltip|
48
+ tooltip.with_trigger(variant: :outline) { "Below" }
49
+ "side: :bottom, align: :start"
50
+ end
51
+ end
52
+
53
+ # the provider-style opt-back for hint-dense screens: the source's provider
54
+ # default is 0ms; 700ms is the recommended delay when a page is
55
+ # dense with tooltips.
56
+ def delayed
57
+ render_component(delay_duration: 700) do |tooltip|
58
+ tooltip.with_trigger(variant: :outline) { "Patient hover" }
59
+ "Opens after 700ms"
60
+ end
61
+ end
62
+
63
+ # disable_hoverable_content: true - pointing at the card itself
64
+ # never holds it open (the strict hover mode).
65
+ def not_hoverable
66
+ render_component(disable_hoverable_content: true) do |tooltip|
67
+ tooltip.with_trigger(variant: :outline) { "Hover" }
68
+ "Closes the moment you leave the trigger"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,25 @@
1
+ <%# ONE provider scope wrapping a control row: the first tooltip opens
2
+ delayed, sweeping along the row opens instantly (the warm grace), and
3
+ the scope cools 300ms after leaving - the toolbar recipe. %>
4
+ <div class="p-4">
5
+ <%= poetry_tooltip_provider(delay_duration: 700, class: "flex items-center gap-2") do %>
6
+ <%= poetry_tooltip do |tooltip| %>
7
+ <% tooltip.with_trigger(variant: :outline, size: :icon, label: "Add") do %>
8
+ <%= poetry_icon(name: :plus) %>
9
+ <% end %>
10
+ Add
11
+ <% end %>
12
+ <%= poetry_tooltip do |tooltip| %>
13
+ <% tooltip.with_trigger(variant: :outline, size: :icon, label: "Confirm") do %>
14
+ <%= poetry_icon(name: :check) %>
15
+ <% end %>
16
+ Confirm
17
+ <% end %>
18
+ <%= poetry_tooltip do |tooltip| %>
19
+ <% tooltip.with_trigger(variant: :outline, size: :icon, label: "Dismiss") do %>
20
+ <%= poetry_icon(name: :x) %>
21
+ <% end %>
22
+ Dismiss
23
+ <% end %>
24
+ <% end %>
25
+ </div>
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Tooltip
6
+ # Re-expressed through the cn-* theme layer. The inverse scheme
7
+ # (bg-foreground/text-background) stays INLINE per upstream's own
8
+ # split - it IS the treatment and upstream keeps it in markup; the
9
+ # box, type, and animation chain ride the theme (animate-in remains
10
+ # UNGATED there, source-exact).
11
+ class Style < Poetry::Core::Style
12
+ element :content, "cn-tooltip-content z-50 w-fit origin-(--transform-origin) bg-foreground " \
13
+ "text-background max-w-xs"
14
+
15
+ # The arrow: popper positions/rotates the outer box; the visual
16
+ # size/rounding rides the theme, the paint + geometry stay inline.
17
+ # block: the diamond is a bare span - inline boxes IGNORE size-2.5,
18
+ # which left the arrow 0x0 (invisible) in every theme since the port.
19
+ element :arrow, "cn-tooltip-arrow z-50 block translate-y-[calc(-50%_-_2px)] rotate-45 " \
20
+ "bg-foreground fill-foreground"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ <%# The flat treegrid: every row a sibling, hierarchy in aria-* computed
2
+ server-side, rows under collapsed ancestors hidden. %>
3
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
4
+ <% if rows.any? %>
5
+ <% rows.each_with_index do |row, index| %>
6
+ <%= content_tag(:div, **Poetry::Core::HTML::Attributes.new(row_attributes(row, index)).to_attributes) do %>
7
+ <span role="gridcell" class="<%= css(:cell) %>">
8
+ <% if row.expandable %>
9
+ <%= content_tag(:button, **Poetry::Core::HTML::Attributes.new(toggle_attributes(row, index)).to_attributes) do %>
10
+ <%= render Poetry::Ui::Icon::Component.new(name: :"chevron-right") %>
11
+ <% end %>
12
+ <% else %>
13
+ <span class="<%= css(:toggle_spacer) %>" aria-hidden="true"></span>
14
+ <% end %>
15
+ <% if row.href.present? && !row.disabled %>
16
+ <a href="<%= row.href %>" data-slot="tree-item-label" class="<%= css(:label) %>" tabindex="-1"><%= row.text %></a>
17
+ <% else %>
18
+ <span data-slot="tree-item-label" class="<%= css(:label) %>"><%= row.text %></span>
19
+ <% end %>
20
+ </span>
21
+ <% end %>
22
+ <% end %>
23
+ <% else %>
24
+ <%# An empty treegrid with bare content is invalid ARIA - the empty
25
+ state wraps in a placeholder row > gridcell (the contract). %>
26
+ <div role="row" aria-level="1" data-slot="tree-item" data-value tabindex="-1" class="<%= css(:item) %>" style="--poetry-tree-level: 1">
27
+ <span role="gridcell" class="<%= css(:cell) %>">
28
+ <span data-slot="tree-item-label" class="text-muted-foreground"><%= t("poetry.tree.empty") %></span>
29
+ </span>
30
+ </div>
31
+ <% end %>
32
+ <% end %>
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Hierarchical expandable lists.
6
+ module Tree
7
+ # A hierarchical expandable list - file navigators, nested
8
+ # categories, org structures. The DOM is a flat list of sibling
9
+ # rows; hierarchy lives entirely in server-computed
10
+ # aria-level/posinset/setsize, so no nested markup is needed and
11
+ # the whole tree server-renders. Rows under a collapsed ancestor
12
+ # render hidden. Arrow keys move over visible rows,
13
+ # ArrowLeft/Right collapse and expand (ArrowLeft on a leaf walks to
14
+ # the parent), Enter/press toggles, and typing jumps to a matching
15
+ # row.
16
+ #
17
+ # Items build through a nested builder (see with_item). Expansion
18
+ # state lives in the DOM - persist it by listening for
19
+ # poetry:tree:toggle and re-rendering with expanded: from your
20
+ # store. href: items navigate; a Tree offers no selection modes.
21
+ #
22
+ # @example
23
+ # <%= poetry_tree(label: "Files") do |tree| %>
24
+ # <% tree.with_item(text: "docs", value: "docs", expanded: true) do |docs| %>
25
+ # <% docs.with_item(text: "intro.md", value: "intro", href: "/docs/intro") %>
26
+ # <% end %>
27
+ # <% end %>
28
+ class Component < Poetry::Core::Component
29
+ # Projected into the registry, llms.txt, and the agent surface.
30
+ AGENT_RULES = [
31
+ "Hierarchical expandable lists are a Tree - never hand-rolled nested <ul>s with " \
32
+ "click handlers; the treegrid semantics, expansion keys, and focus rules ride the " \
33
+ "controller.",
34
+ "label: is REQUIRED (the treegrid's accessible name).",
35
+ "Items: tree.with_item(text:, value:, expanded:, disabled:, href:) with nesting via " \
36
+ "the block - the component flattens and computes aria-level/posinset/setsize.",
37
+ "Expansion is client state; persist it by listening for poetry:tree:toggle and " \
38
+ "re-rendering with expanded: from your store.",
39
+ "Navigation destinations take href: (the label renders as a link); a Tree is not a " \
40
+ "menu - actions belong to DropdownMenu, picking to Select/Combobox."
41
+ ].freeze
42
+
43
+ use_stimulus do
44
+ on :root do
45
+ controller :tree do
46
+ register
47
+ action :keydown, on: :keydown
48
+ action :press, on: :click
49
+ end
50
+ end
51
+ on :toggle do
52
+ controller :tree do
53
+ action :pressStart, on: :pointerdown
54
+ action :toggle, on: :click
55
+ end
56
+ end
57
+ end
58
+
59
+ option :label, :string, required: true, doc: "The tree's accessible name. Required."
60
+
61
+ part "tree", "The treegrid container (role=treegrid, the accessible name) - roving " \
62
+ "focus, expansion keys, and typeahead ride here; rows are FLAT siblings"
63
+ part "tree-item", "One row (role=row > gridcell) - hierarchy in aria-level/posinset/" \
64
+ "setsize, indentation via --poetry-tree-level; rows under a " \
65
+ "collapsed ancestor render hidden",
66
+ states: {
67
+ "data-expanded" => "the row's subtree is open (parents only; aria-expanded " \
68
+ "is the canonical twin)",
69
+ "data-disabled" => "the item is disabled (skipped by arrows and typeahead)",
70
+ "data-value" => { condition: "always - the toggle event's identity" },
71
+ "data-level" => { condition: "always - the 1-based depth (aria-level's twin; " \
72
+ "--poetry-tree-level drives the indent)" }
73
+ },
74
+ vars: {
75
+ "--poetry-tree-level" => "the 1-based depth - indentation is " \
76
+ "calc((level - 1) * step) in the dictionary"
77
+ }
78
+ part "tree-item-toggle", "The chevron (parents only): tabindex -1, never steals " \
79
+ "focus, aria-label flips Expand/Collapse",
80
+ states: {
81
+ "data-expand-label" => "always - the localized Expand string the controller " \
82
+ "swaps in on collapse",
83
+ "data-collapse-label" => "always - the localized Collapse string the " \
84
+ "controller swaps in on expand"
85
+ }
86
+ part "tree-item-label", "The row's text - a link when href: is given"
87
+
88
+ # @api private
89
+ def before_render
90
+ raise ArgumentError, "Tree requires label: (the treegrid's accessible name)" if label.blank?
91
+
92
+ # Consume the content block: it runs for its with_item side
93
+ # effects (the builder API), never for output.
94
+ content
95
+ end
96
+
97
+ # Declares one row. Nest children by calling with_item again on the
98
+ # yielded builder. Keywords: text: (the row's label, required), value:
99
+ # (the toggle event's identity, defaults to text:), expanded: (render
100
+ # the subtree open), disabled:, href: (the label renders as a link).
101
+ #
102
+ # @example
103
+ # tree.with_item(text: "docs", value: "docs", expanded: true) do |docs|
104
+ # docs.with_item(text: "intro.md", href: "/docs/intro")
105
+ # end
106
+ def with_item(**, &block)
107
+ root_items << Item.new(**).tap { |item| block&.call(item) }
108
+ nil
109
+ end
110
+
111
+ # @api private
112
+ def root_items
113
+ @root_items ||= []
114
+ end
115
+
116
+ # @api private
117
+ def rows
118
+ @rows ||= flatten(root_items, level: 1, hidden: false)
119
+ end
120
+
121
+ # @api private
122
+ def root_attributes
123
+ html_attributes.merge_if_not_set(
124
+ {
125
+ "data-slot" => "tree", "role" => "treegrid",
126
+ "aria-label" => label, "class" => css
127
+ }.merge(component_data_attributes).merge(stimulus_attributes_for(:root))
128
+ )
129
+ end
130
+
131
+ # @api private
132
+ def row_attributes(row, index)
133
+ attrs = {
134
+ "id" => row_id(index), "role" => "row", "data-slot" => "tree-item",
135
+ "data-value" => row.value, "data-level" => row.level,
136
+ "aria-level" => row.level, "aria-posinset" => row.posinset,
137
+ "aria-setsize" => row.setsize,
138
+ "tabindex" => index == first_visible_index ? "0" : "-1",
139
+ "style" => "--poetry-tree-level: #{row.level}",
140
+ "class" => css(:item)
141
+ }
142
+ attrs["aria-expanded"] = row.expanded.to_s if row.expandable
143
+ attrs["data-expanded"] = "" if row.expandable && row.expanded
144
+ if row.disabled
145
+ attrs["data-disabled"] = ""
146
+ attrs["aria-disabled"] = "true"
147
+ end
148
+ attrs["hidden"] = "" if row.hidden
149
+ attrs
150
+ end
151
+
152
+ # @api private
153
+ def toggle_attributes(row, index)
154
+ {
155
+ "type" => "button", "tabindex" => "-1",
156
+ "data-slot" => "tree-item-toggle", "class" => css(:toggle),
157
+ "aria-label" => row.expanded ? t("poetry.tree.collapse") : t("poetry.tree.expand"),
158
+ "aria-labelledby" => "#{row_id(index)}-toggle #{row_id(index)}",
159
+ "id" => "#{row_id(index)}-toggle",
160
+ "data-expand-label" => t("poetry.tree.expand"),
161
+ "data-collapse-label" => t("poetry.tree.collapse")
162
+ }.merge(stimulus_attributes_for(:toggle))
163
+ end
164
+
165
+ # @api private
166
+ def row_id(index)
167
+ "#{tree_id}-item-#{index}"
168
+ end
169
+
170
+ # @api private
171
+ def tree_id
172
+ @tree_id ||= poetry_instance_id("poetry-tree")
173
+ end
174
+
175
+ # @api private
176
+ def first_visible_index
177
+ @first_visible_index ||= rows.index { |row| !row.hidden } || 0
178
+ end
179
+
180
+ # One flattened row (built by #flatten below).
181
+ # @api private
182
+ Row = Struct.new(
183
+ :text, :value, :href, :level, :posinset, :setsize,
184
+ :expanded, :expandable, :disabled, :hidden,
185
+ keyword_init: true
186
+ )
187
+
188
+ # The nested builder yielded to item blocks - call with_item on it
189
+ # to declare children. Never constructed directly.
190
+ class Item
191
+ # @api private
192
+ attr_reader :options, :children
193
+
194
+ # @api private
195
+ def initialize(**options)
196
+ @options = options
197
+ @children = []
198
+ end
199
+
200
+ # Declares a child row; takes the same keywords as the
201
+ # component's with_item and yields its own builder for nesting.
202
+ def with_item(**)
203
+ child = Item.new(**)
204
+ @children << child
205
+ yield child if block_given?
206
+ child
207
+ end
208
+ end
209
+
210
+ private
211
+
212
+ def flatten(items, level:, hidden:)
213
+ items.flat_map.with_index do |item, index|
214
+ expandable = item.children.any?
215
+ expanded = expandable && item.options.fetch(:expanded, false)
216
+ row = Row.new(
217
+ text: item.options.fetch(:text), value: item.options[:value] || item.options.fetch(:text),
218
+ href: item.options[:href], level: level,
219
+ posinset: index + 1, setsize: items.size,
220
+ expanded: expanded, expandable: expandable,
221
+ disabled: item.options.fetch(:disabled, false), hidden: hidden
222
+ )
223
+
224
+ [row] + flatten(item.children, level: level + 1, hidden: hidden || !expanded)
225
+ end
226
+ end
227
+
228
+ private :root_items, :rows, :root_attributes, :row_attributes, :toggle_attributes, :row_id, :tree_id
229
+ private :first_visible_index
230
+ end
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Tree
6
+ class Preview < Poetry::Core::Preview::Base
7
+ def default
8
+ render_component(label: "Files") do |tree|
9
+ tree.with_item(text: "docs", value: "docs", expanded: true) do |docs|
10
+ docs.with_item(text: "guides", value: "guides") do |guides|
11
+ guides.with_item(text: "intro.md", value: "intro")
12
+ guides.with_item(text: "advanced.md", value: "advanced")
13
+ end
14
+ docs.with_item(text: "README.md", value: "docs-readme")
15
+ end
16
+ tree.with_item(text: "src", value: "src") do |src|
17
+ src.with_item(text: "main.rb", value: "main")
18
+ end
19
+ tree.with_item(text: "LICENSE", value: "license")
20
+ end
21
+ end
22
+
23
+ # Server-persisted expansion: collapsed parents render their
24
+ # subtree hidden; nested collapsed state survives cycles.
25
+ def deeply_nested
26
+ render_component(label: "Organization") do |tree|
27
+ tree.with_item(text: "Engineering", value: "eng", expanded: true) do |eng|
28
+ eng.with_item(text: "Platform", value: "platform", expanded: true) do |platform|
29
+ platform.with_item(text: "Infra", value: "infra") do |infra|
30
+ infra.with_item(text: "On-call", value: "oncall")
31
+ end
32
+ end
33
+ eng.with_item(text: "Product", value: "product", disabled: true)
34
+ end
35
+ tree.with_item(text: "Design", value: "design")
36
+ end
37
+ end
38
+
39
+ # href: items render their labels as links (a file navigator).
40
+ def with_links
41
+ render_component(label: "Pages") do |tree|
42
+ tree.with_item(text: "Getting started", value: "start", expanded: true) do |start|
43
+ start.with_item(text: "Install", value: "install", href: "#install")
44
+ start.with_item(text: "Theming", value: "theming", href: "#theming")
45
+ end
46
+ end
47
+ end
48
+
49
+ def empty
50
+ render_component(label: "Files")
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Tree
6
+ # Utility-only (the Separator/Spinner rule): rows indent off the
7
+ # server-stamped --poetry-tree-level custom property, the chevron
8
+ # rotates on the row's data-expanded (group-scoped), and focus rides
9
+ # a ring on the row itself. data-slot selectors are the restyle seam.
10
+ class Style < Poetry::Core::Style
11
+ base "flex w-full flex-col gap-px outline-none"
12
+
13
+ element :item, "group/tree-item flex cursor-default items-center gap-1.5 rounded-md " \
14
+ "py-1.5 pr-2 text-sm outline-none " \
15
+ "pl-[calc(0.5rem+(var(--poetry-tree-level)-1)*1.25rem)] " \
16
+ "hover:bg-accent/50 " \
17
+ "focus-visible:ring-[3px] focus-visible:ring-ring/50 " \
18
+ "data-disabled:pointer-events-none data-disabled:opacity-50"
19
+ element :cell, "contents"
20
+ element :toggle, "inline-flex size-4 shrink-0 items-center justify-center rounded-sm " \
21
+ "text-muted-foreground outline-none transition-transform " \
22
+ "group-data-[expanded]/tree-item:rotate-90 [&_svg]:size-3.5"
23
+ # Leaves keep the chevron's footprint so labels align across levels.
24
+ element :toggle_spacer, "inline-flex size-4 shrink-0"
25
+ element :label, "truncate text-foreground no-underline"
26
+ end
27
+ end
28
+ end
29
+ end