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,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Poetry
6
+ module Ui
7
+ # Deferred family: lazy-loaded regions with loading and error states.
8
+ module Deferred
9
+ # A deferred region: a Turbo Frame that fetches its content when it
10
+ # becomes VISIBLE (loading: :lazy, the default - so a deferred Tabs
11
+ # panel or HoverCard body loads on first reveal with no extra
12
+ # wiring) or right after paint (:eager). The block is the loading
13
+ # placeholder (a Skeleton renders when absent), and a failed fetch
14
+ # shows a visible, retryable error card instead of silent
15
+ # blankness.
16
+ #
17
+ # @example
18
+ # render Poetry::Ui::Deferred::Component.new(src: "/dashboard/activity")
19
+ class Component < Poetry::Core::Component
20
+ # Projected into the registry, llms.txt, and the agent surface.
21
+ AGENT_RULES = [
22
+ "Use poetry_deferred(src:) for expensive regions - never a spinner div + a hand-rolled fetch.",
23
+ "loading: :lazy (the default) fetches on visibility: a deferred region inside a hidden " \
24
+ "Tabs panel (with_tab defer:) or HoverCard (defer:) loads on first reveal for free.",
25
+ "The block is the placeholder (a Skeleton renders when absent); failure shows a retryable " \
26
+ "error card automatically - never hand-wire loading or error states around it."
27
+ ].freeze
28
+
29
+ use_stimulus do
30
+ # src rides the controller value, NOT the frame markup: connect()
31
+ # arms it, so a fast response can never beat the controllers
32
+ # module graph to the frame.
33
+ on :root do
34
+ controller :deferred do
35
+ register
36
+ value :src
37
+ end
38
+ end
39
+ # Template-consumed elements (previously hand-written strings).
40
+ on :placeholder do
41
+ controller(:deferred) { target :placeholder }
42
+ end
43
+ on :error_template do
44
+ controller(:deferred) { target :error }
45
+ end
46
+ on :retry do
47
+ controller(:deferred) { action :retry, on: :click }
48
+ end
49
+ end
50
+
51
+ option :src, :string, required: true, doc: "The URL to fetch - required; rendering without it raises."
52
+ option :loading, :symbol, default: :lazy,
53
+ doc: ":lazy fetches when the frame becomes visible; :eager fetches right after paint."
54
+
55
+ part "deferred", "The <turbo-frame> root - src is armed at connect(); failure is " \
56
+ "a state reflected here, never silent blankness",
57
+ states: {
58
+ "data-error" => "a frame fetch failed (error response, missing frame, or " \
59
+ "network error) - the controller stamps the error card; " \
60
+ "retry clears it"
61
+ }
62
+ part "deferred-error", "The retryable error card, stamped into the frame from the " \
63
+ "slotted <template> on failure"
64
+
65
+ # Enforces the required src.
66
+ # @api private
67
+ def before_render
68
+ raise ArgumentError, "poetry_deferred requires src:" if src.blank?
69
+ end
70
+
71
+ # No src in the markup ON PURPOSE: the URL rides the controller
72
+ # value and connect() arms it, so a fast response can never beat
73
+ # the controllers module graph to the frame (Turbo 8's
74
+ # frame-missing default would promote that race to a full-page
75
+ # visit).
76
+ # @api private
77
+ def root_attributes
78
+ html_attributes.merge_if_not_set(
79
+ {
80
+ "id" => "poetry-deferred-#{Digest::MD5.hexdigest(src.to_s).first(8)}",
81
+ "loading" => loading, "data-slot" => "deferred"
82
+ }.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
83
+ )
84
+ end
85
+
86
+ private :root_attributes
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Deferred
6
+ # The Deferred preview. The dummy loads no Turbo, so the frame stays
7
+ # inert: what renders (and what the baseline captures) IS the
8
+ # placeholder state - deterministic by construction. Live loading /
9
+ # error / retry behavior is proven in poetry-core's deferred.test.js
10
+ # and on the docs site's /deferred page.
11
+ class Preview < Poetry::Core::Preview::Base
12
+ def default
13
+ render_component(src: "/previews/activity")
14
+ end
15
+
16
+ def custom_placeholder
17
+ render_component(src: "/previews/activity") do
18
+ "Fetching the latest activity…"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
2
+ <%= trigger %>
3
+ <%= content_tag(:dialog, **Poetry::Core::HTML::Attributes.new(dialog_attributes).to_attributes) do %>
4
+ <div data-slot="dialog-header" class="<%= css(:header) %>">
5
+ <h2 id="<%= title_id %>" data-slot="dialog-title" class="<%= css(:title) %>"><%= title %></h2>
6
+ <% if description? %>
7
+ <p id="<%= description_id %>" data-slot="dialog-description" class="<%= css(:description) %>"><%= description %></p>
8
+ <% end %>
9
+ </div>
10
+ <%= content %>
11
+ <% if footer? %><div data-slot="dialog-footer" class="<%= css(:footer) %>"><%= footer %></div><% end %>
12
+ <% if show_close_button %>
13
+ <%= render Poetry::Ui::Button::Component.new(variant: :ghost, size: :"icon-sm",
14
+ label: t("poetry.dialog.close"),
15
+ class: css(:close),
16
+ data: { slot: "dialog-close", action: close_action }) do %>
17
+ <%= render Poetry::Ui::Icon::Component.new(name: :x) %>
18
+ <% end %>
19
+ <% end %>
20
+ <% end %>
21
+ <% end %>
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Dialog family: the modal overlay on the native <dialog> element.
6
+ module Dialog
7
+ # A modal dialog built ON the platform: a native <dialog> +
8
+ # showModal() owns focus trapping, Esc, top-layer stacking, and
9
+ # focus return; poetry adds the data-open/data-closed pair,
10
+ # backdrop dismissal, and the scroll lock. The title is REQUIRED
11
+ # (the accessible name - aria-labelledby is always wired).
12
+ #
13
+ # @example A confirmation dialog
14
+ # render Poetry::Ui::Dialog::Component.new do |dialog|
15
+ # dialog.with_trigger(variant: :outline) { "Open" }
16
+ # dialog.with_title { "Are you sure?" }
17
+ # dialog.with_description { "This cannot be undone." }
18
+ # end
19
+ class Component < Poetry::Core::Component
20
+ include Poetry::Ui::ComposableTrigger
21
+ include Poetry::Ui::FamilyIdentity
22
+
23
+ # Projected into the registry, llms.txt, and the agent surface.
24
+ AGENT_RULES = [
25
+ ComposableTrigger::AGENT_RULE,
26
+ "Open dialogs with with_trigger(...) - never a hand-wired button.",
27
+ "with_title is REQUIRED (the accessible name); with_description when the purpose needs explaining.",
28
+ "Confirmations that must not be lost use dismissible: false (backdrop clicks stop closing).",
29
+ "show_close_button: false removes the corner X - keep a footer action (Esc still closes).",
30
+ "Destructive confirmations pair a destructive Button in the footer - never auto-submit."
31
+ ].freeze
32
+
33
+ # The required slots, stated statically so static checks can flag
34
+ # a missing title without rendering.
35
+ REQUIRED_SLOTS = { title: "the accessible name" }.freeze
36
+
37
+ # The forwarding-lambda fact: with_trigger renders a Button -
38
+ # callers get Button's full typed-slot contract statically.
39
+ SLOT_RENDERS = { trigger: Button::Component }.freeze
40
+
41
+ renders_one :trigger,
42
+ doc: "The trigger is a poetry Button wired to open the dialog - agents pass Button props: " \
43
+ "with_trigger(variant: :outline) { \"Open\" }.",
44
+ renders: lambda { |**options, &block|
45
+ composed_trigger({ "data-action" => stimulus_action(:open) }, options, &block) || begin
46
+ merger = Poetry::Core::Config.current.stimulus_merger
47
+ wired_data = { action: stimulus_action(:open) }
48
+ options[:data] = wired_data.merge(options[:data] || {}) do |key, wired, caller|
49
+ key == :action ? merger.merge_actions(wired, caller) : caller
50
+ end
51
+ Button::Component.new(**options, &block)
52
+ end
53
+ }
54
+ renders_one :title, doc: "The heading - the dialog's accessible name; required."
55
+ renders_one :description, doc: "Muted copy under the title, wired to aria-describedby."
56
+ renders_one :footer, doc: "The action row at the bottom of the panel."
57
+
58
+ # Sheet and Drawer subclass this and REDECLARE both elements with
59
+ # their own controllers (replace-on-redeclare); the trigger lambda
60
+ # and close_action late-bind through stimulus_action, so subclasses
61
+ # never re-type the entry points.
62
+ use_stimulus do
63
+ on :root do
64
+ controller :dialog do
65
+ register
66
+ value :dismissible
67
+ end
68
+ end
69
+ on :content do
70
+ controller :dialog do
71
+ target :dialog
72
+ action :close, on: :cancel
73
+ action :backdrop_close, on: :click
74
+ end
75
+ end
76
+ # Forwarded descriptors (bare = element-default click): the
77
+ # trigger Button and the corner close button.
78
+ on :trigger do
79
+ controller(:dialog) { action :open }
80
+ end
81
+ on :close do
82
+ controller(:dialog) { action :close }
83
+ end
84
+ end
85
+
86
+ # The operate surface: open/close a rendered dialog (registered per
87
+ # instance, opt-in - see poetry-agent). Bare actions on purpose:
88
+ # Sheet and Drawer re-controller the root, and a bare `executes:`
89
+ # re-resolves to THEIR controller at projection time.
90
+ tool :open,
91
+ description: "Open the dialog.",
92
+ executes: :open,
93
+ mutating: true
94
+ tool :close,
95
+ description: "Close the dialog.",
96
+ executes: :close,
97
+ mutating: true
98
+
99
+ option :dismissible, :boolean, default: true,
100
+ doc: "Backdrop clicks close the dialog; false keeps confirmations from being " \
101
+ "dismissed accidentally (Esc still closes)."
102
+
103
+ option :show_close_button, :boolean, default: true,
104
+ doc: "Renders the corner X; false forces a deliberate footer choice " \
105
+ "(footer actions and Esc remain). Sheet inherits this."
106
+ option :content_class, :string,
107
+ doc: "Extra classes merged onto the <dialog> panel (e.g. \"max-h-[50vh]\" caps a top/bottom sheet)."
108
+
109
+ part "dialog", "Root wrapper around the trigger and the <dialog> element"
110
+ part "dialog-content", "The <dialog> panel - positioning, animation, and the open " \
111
+ "state ride here",
112
+ states: {
113
+ "data-open" => "panel is open (the controller flips the pair at runtime)",
114
+ "data-closed" => "panel is closed or animating out (the server-rendered state)"
115
+ }
116
+ part "dialog-header", "Title block at the top of the panel"
117
+ part "dialog-title", "The heading - the dialog's accessible name (required slot)"
118
+ part "dialog-description", "Muted copy under the title, wired to aria-describedby"
119
+ part "dialog-footer", "Action row at the bottom of the panel"
120
+
121
+ # Enforces the required title.
122
+ # @api private
123
+ def before_render
124
+ raise ArgumentError, "#{family_name} requires with_title (the accessible name)" unless title?
125
+ end
126
+
127
+ # The title's id - the aria-labelledby target.
128
+ # @api private
129
+ def title_id
130
+ "#{instance_id}-title"
131
+ end
132
+
133
+ # The description's id - the aria-describedby target.
134
+ # @api private
135
+ def description_id
136
+ "#{instance_id}-description"
137
+ end
138
+
139
+ # Attributes for the <dialog> panel.
140
+ # @api private
141
+ def dialog_attributes
142
+ attrs = {
143
+ "class" => css(:content, class: panel_classes),
144
+ "data-slot" => "#{family_slot_prefix}-content",
145
+ **panel_stamps,
146
+ "data-closed" => "",
147
+ "aria-labelledby" => title_id
148
+ }.merge(stimulus_attributes_for(:content))
149
+ attrs["aria-describedby"] = description_id if description?
150
+ attrs
151
+ end
152
+
153
+ # Validated action descriptor for the template's close button -
154
+ # resolves against the class's OWN declarations (Sheet/Drawer get
155
+ # their controller without overriding).
156
+ # @api private
157
+ def close_action
158
+ stimulus_action(:close)
159
+ end
160
+
161
+ # The dialog-family root wrapper is NON-VISUAL - the <dialog> is
162
+ # the visual root. The style resolver renders variant branches only
163
+ # at the dictionary root, so subclasses with a root-level style
164
+ # axis (Sheet's side:, Drawer's direction:) merge the branch into
165
+ # :content themselves - and the inherited html_attributes would
166
+ # paint the same branch on the in-flow wrapper too (the closed
167
+ # Drawer drew its themed border + w-full across the docs mounts).
168
+ # Only the tailwind resolution is stripped: in :bem mode the root
169
+ # block--modifier tokens ARE the host's styling contract.
170
+ # @api private
171
+ def html_attributes
172
+ return super unless Poetry::Core::Config.current.css_mode == :tailwind
173
+
174
+ @html_attributes.merge(class: classnames(@html_attributes[:class]))
175
+ end
176
+
177
+ private
178
+
179
+ # Subclass seams for the panel: extra class values and the family
180
+ # stamps between data-slot and data-closed (Sheet stamps its side,
181
+ # Drawer its swipe direction).
182
+ def panel_classes
183
+ content_class
184
+ end
185
+
186
+ def panel_stamps
187
+ {}
188
+ end
189
+
190
+ private :title_id, :description_id, :dialog_attributes, :close_action, :html_attributes
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Dialog
6
+ class Preview < Poetry::Core::Preview::Base
7
+ def default
8
+ render_component do |dialog|
9
+ dialog.with_trigger(variant: :outline) { "Edit profile" }
10
+ dialog.with_title { "Edit profile" }
11
+ dialog.with_description { "Make changes and save when done." }
12
+ dialog.with_footer do
13
+ embed(Poetry::Ui::Button::Component.new.with_content("Save changes"))
14
+ end
15
+ "Profile form goes here."
16
+ end
17
+ end
18
+
19
+ def confirmation
20
+ render_component(dismissible: false) do |dialog|
21
+ dialog.with_trigger(variant: :destructive) { "Delete account" }
22
+ dialog.with_title { "Are you absolutely sure?" }
23
+ dialog.with_description { "This permanently deletes your account and all data." }
24
+ dialog.with_footer do
25
+ embed(Poetry::Ui::Button::Component.new(variant: :destructive).with_content("Delete"))
26
+ end
27
+ ""
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Dialog
6
+ # The root is a non-visual wrapper; the visual surface is the native
7
+ # <dialog> element (the :content element), centered by the top layer.
8
+ # Re-expressed through the cn-* theme layer: the panel chrome,
9
+ # backdrop tint, and enter animation ride themes/default.css; the
10
+ # native-dialog mechanisms stay inline.
11
+ class Style < Poetry::Core::Style
12
+ # open:grid, NOT grid: a bare display class would defeat the UA's
13
+ # dialog:not([open]) { display: none } and render the dialog inline
14
+ # while closed (invisible to jsdom, which has no UA stylesheet).
15
+ # No `relative`: a modal <dialog> lives in the top layer, where the
16
+ # browser drops a `position: relative` back to the UA `absolute` and
17
+ # pins the panel to the DOCUMENT origin (it then scrolls off-screen
18
+ # when opened below the fold). Letting position fall through to the
19
+ # UA `:modal` rule keeps it viewport-fixed and centered.
20
+ element :content, "cn-dialog-content m-auto open:grid"
21
+
22
+ element :header, "cn-dialog-header flex flex-col"
23
+ element :title, "cn-dialog-title"
24
+ element :description, "cn-dialog-description"
25
+ element :footer, "cn-dialog-footer flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"
26
+ element :close, "cn-dialog-close"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
2
+ <%= trigger %>
3
+ <%= content_tag(:dialog, **Poetry::Core::HTML::Attributes.new(dialog_attributes).to_attributes) do %>
4
+ <% if show_swipe_handle %>
5
+ <div data-slot="drawer-swipe-handle" aria-hidden="true" class="<%= css(:handle) %>"></div>
6
+ <% end %>
7
+ <div data-slot="drawer-header" class="<%= css(:header) %>">
8
+ <h2 id="<%= title_id %>" data-slot="drawer-title" class="<%= css(:title) %>"><%= title %></h2>
9
+ <% if description? %>
10
+ <p id="<%= description_id %>" data-slot="drawer-description" class="<%= css(:description) %>"><%= description %></p>
11
+ <% end %>
12
+ </div>
13
+ <div data-slot="drawer-body" class="<%= css(:body) %>"><%= content %></div>
14
+ <% if footer? %><div data-slot="drawer-footer" class="<%= css(:footer) %>"><%= footer %></div><% end %>
15
+ <% end %>
16
+ <% end %>
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Drawer family: the swipeable edge sheet on the dialog spine.
6
+ module Drawer
7
+ # A drawer: an edge-anchored dialog you can SWIPE away, most at
8
+ # home as the mobile bottom sheet. Everything hard is inherited
9
+ # from Dialog (the native <dialog> focus trap, required title,
10
+ # dismissible:); the drawer adds the swipe gesture, the
11
+ # edge-rounded popup with transition-driven enter/exit, the
12
+ # optional grab handle, and snap_points: preset resting heights
13
+ # for bottom sheets.
14
+ #
15
+ # Nested drawer stacking visuals and bleed are not supported.
16
+ #
17
+ # @example
18
+ # render Poetry::Ui::Drawer::Component.new(show_swipe_handle: true) do |drawer|
19
+ # drawer.with_trigger { "Open drawer" }
20
+ # drawer.with_title { "Move goal" }
21
+ # drawer.with_description { "Set your daily activity goal." }
22
+ # "Drawer body"
23
+ # end
24
+ class Component < Dialog::Component
25
+ # The closed vocabulary for the direction axis.
26
+ DIRECTIONS = %i[down up left right].freeze
27
+
28
+ # Accepted CSS length spellings for snap points (px or rem).
29
+ SNAP_POINT_LENGTH = /\A\d+(\.\d+)?(px|rem)\z/
30
+
31
+ # Projected into the registry, llms.txt, and the agent surface.
32
+ AGENT_RULES = [
33
+ "Open drawers with with_trigger(...) - never a hand-wired button.",
34
+ "with_title is REQUIRED (the accessible name) - the inherited Dialog rule.",
35
+ "direction: is the DISMISS direction: :down is the mobile bottom sheet (the default); " \
36
+ "left/right make an edge panel - prefer Sheet on desktop.",
37
+ "show_swipe_handle: true renders the grab pill - use it on bottom sheets so the " \
38
+ "gesture is discoverable.",
39
+ "Esc and the backdrop still dismiss (the platform trap) - the swipe is an addition, " \
40
+ "never the only way out.",
41
+ "modal: false keeps the page interactive (no scrim, no focus trap) - pair a wired " \
42
+ "footer close; Esc while focus is inside still exits.",
43
+ "snap_points: [\"31rem\", 1] snaps a bottom sheet between preset heights (ascending " \
44
+ "fractions or px/rem lengths; opens at the first) - direction: :down only."
45
+ ].freeze
46
+
47
+ # Replace-on-redeclare: both elements re-controller to :drawer;
48
+ # the inherited trigger lambda late-binds through stimulus_action.
49
+ use_stimulus do
50
+ on :root do
51
+ controller :drawer do
52
+ register
53
+ value :dismissible
54
+ value :direction
55
+ value :modal
56
+ value :snap_points, from: :snap_points_json, if: -> { snap_points.present? }
57
+ end
58
+ end
59
+ on :content do
60
+ controller :drawer do
61
+ target :dialog
62
+ action :close, on: :cancel
63
+ action :backdrop_close, on: :click
64
+ # A non-modal dialog never fires cancel - Esc rides its own
65
+ # keydown exit (guarded controller-side to modal: false).
66
+ action :escape_close, on: :keydown, unless: :modal
67
+ action :swipe_start, on: :pointerdown
68
+ action :swipe_move, on: :pointermove
69
+ action :swipe_end, on: :pointerup
70
+ action :swipe_cancel, on: :pointercancel
71
+ end
72
+ end
73
+ on :trigger do
74
+ controller(:drawer) { action :open }
75
+ end
76
+ # The Drawer renders no corner close button - an empty
77
+ # redeclaration ERASES Dialog's inherited :close element (and
78
+ # with it the dialog identifier that would make unqualified
79
+ # stimulus_action ambiguous).
80
+ on :close
81
+ end
82
+
83
+ style :direction, default: :down, required: true, variants: DIRECTIONS,
84
+ doc: "The dismiss direction - :down is the mobile bottom sheet; the edge chrome and swipe " \
85
+ "axis derive from it."
86
+
87
+ option :show_swipe_handle, :boolean, default: false,
88
+ doc: "Renders the grab pill so the swipe gesture is discoverable."
89
+
90
+ option :modal, :boolean, default: true,
91
+ doc: "Non-modal (false) opens with show() - no top layer, no scrim, no focus trap, " \
92
+ "no scroll lock; the page behind stays interactive. Esc (while focus is " \
93
+ "inside), the swipe, and any wired close button still exit; there is no " \
94
+ "backdrop to click, so pointer dismissal is off by nature."
95
+
96
+ option :snap_points, ActiveModel::Type::Value.new,
97
+ doc: "Preset resting heights for a bottom sheet, ascending: fractions of the full height (0..1] or " \
98
+ "CSS px/rem lengths ([\"31rem\", 1]). The popup runs full-height and opens at the first point; " \
99
+ "drags move between points, below the first dismisses. direction: :down only."
100
+
101
+ part "drawer", "Root wrapper around the trigger and the <dialog> element"
102
+ part "drawer-content", "The <dialog> popup - the edge chrome, presence animation, and " \
103
+ "the swipe contract all ride here (::backdrop inherits the " \
104
+ "swipe vars, so the overlay fade rides along)",
105
+ states: {
106
+ "data-open" => "popup is open (the controller flips the pair at runtime)",
107
+ "data-closed" => "popup is closed or animating out (the server-rendered state)",
108
+ "data-swipe-direction" => { condition: "always - the dismiss direction",
109
+ values: DIRECTIONS.map(&:to_s) },
110
+ "data-swiping" => "a pointer drag is tracking (transitions go duration-0 - the " \
111
+ "drawer follows the finger)",
112
+ "data-snap-points" => "snap_points: present - the popup runs full-height and " \
113
+ "--drawer-snap-point-offset rests it at the current point",
114
+ "data-starting-style" => "the enter transition's first frame (the presence " \
115
+ "helper's two-frame trick)",
116
+ "data-ending-style" => "held through the exit transition before the native " \
117
+ "close()"
118
+ },
119
+ vars: {
120
+ "--drawer-swipe-movement-x" => "px dragged toward a left/right dismissal " \
121
+ "(controller-written during swipes)",
122
+ "--drawer-swipe-movement-y" => "px dragged toward an up/down dismissal " \
123
+ "(controller-written during swipes)",
124
+ "--drawer-swipe-progress" => "0..1 fraction of the dismiss travel (the backdrop " \
125
+ "fade rides it)",
126
+ "--drawer-swipe-strength" => "remaining-travel factor set on release - scales " \
127
+ "the exit duration so a mostly-swiped drawer " \
128
+ "closes fast"
129
+ }
130
+ part "drawer-swipe-handle", "The grab pill (show_swipe_handle: true, aria-hidden) - a " \
131
+ "drag may always start on it"
132
+ part "drawer-header", "Title block at the top of the popup"
133
+ part "drawer-title", "The heading - the drawer's accessible name (required slot)"
134
+ part "drawer-description", "Muted copy under the title, wired to aria-describedby"
135
+ part "drawer-body", "The scrollable content region between header and footer"
136
+ part "drawer-footer", "Action row pinned to the bottom of the popup"
137
+
138
+ # Enforces the inherited title contract plus snap-point validity.
139
+ # @api private
140
+ def before_render
141
+ super
142
+ validate_snap_points! if snap_points.present?
143
+ end
144
+
145
+ # The parent's show_close_button does not apply: a Drawer has no
146
+ # corner X (it closes by swipe, backdrop, or footer actions), so
147
+ # the inherited option is hidden from introspection. A projected
148
+ # option the template ignores would be a contract lie.
149
+ # @api private
150
+ def self.option_attributes
151
+ super - %i[show_close_button]
152
+ end
153
+
154
+ # The <dialog> IS the drawer popup: the chrome + swipe wiring land
155
+ # here. The swipe vars are written to this element by the controller
156
+ # and ::backdrop inherits them (the overlay fade rides along).
157
+ # @api private
158
+ def dialog_attributes
159
+ attrs = {
160
+ # Non-modal show() skips the top layer, so the UA :modal
161
+ # positioning (fixed + inset:0) must be re-stated explicitly -
162
+ # the direction margins and over-constraint sizing then work
163
+ # exactly as they do in the top layer.
164
+ "class" => css(:content, class: [Style.direction(direction),
165
+ (css(:nonmodal) unless modal)].compact.join(" ")),
166
+ "data-slot" => "drawer-content",
167
+ "data-swipe-direction" => direction,
168
+ "data-closed" => "",
169
+ "aria-labelledby" => title_id
170
+ }.merge(stimulus_attributes_for(:content))
171
+ # The attribute drives the dictionary's full-height sizing; the
172
+ # controller reads the value for the offset physics.
173
+ attrs["data-snap-points"] = "" if snap_points.present?
174
+ attrs["aria-describedby"] = description_id if description?
175
+ attrs
176
+ end
177
+
178
+ private
179
+
180
+ def validate_snap_points!
181
+ unless direction == :down
182
+ raise ArgumentError, "Drawer snap_points: is a bottom-sheet recipe - direction: :down only"
183
+ end
184
+
185
+ valid = snap_points.is_a?(Array) && snap_points.any? && snap_points.all? do |point|
186
+ (point.is_a?(Numeric) && point.positive? && point <= 1) ||
187
+ (point.is_a?(String) && point.match?(SNAP_POINT_LENGTH))
188
+ end
189
+ return if valid
190
+
191
+ raise ArgumentError, "Drawer snap_points: entries are fractions in (0, 1] or CSS " \
192
+ 'px/rem lengths ("31rem", "400px"), ascending'
193
+ end
194
+
195
+ # Host-facing close descriptor (no template consumer) - the
196
+ # explicit click event preserved from the pre-declaration shape.
197
+ def close_action
198
+ stimulus_action(:close, on: :click)
199
+ end
200
+
201
+ def snap_points_json = snap_points.to_json
202
+
203
+ private :dialog_attributes
204
+ end
205
+ end
206
+ end
207
+ end