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,366 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module Poetry
6
+ module Ui
7
+ # Server-rendered month-grid date pickers.
8
+ module Calendar
9
+ # A month grid for picking a date, computed server-side with Ruby
10
+ # date math: the initial month renders correctly with no JS, and
11
+ # the controller adds navigation and selection on top. Selection is
12
+ # a real form value - name: posts the chosen date as an ISO string
13
+ # in a hidden input (range mode posts name[start] and name[end]).
14
+ # A bare Calendar is the always-visible grid; DatePicker wraps it
15
+ # in a popover behind a text field.
16
+ #
17
+ # @example A form-posting date pick
18
+ # render Poetry::Ui::Calendar::Component.new(name: "event[date]", selected: "2026-07-04")
19
+ class Component < Poetry::Core::Component
20
+ # The date-valued keywords, parsed to Date (or nil) for the template.
21
+ # @api private
22
+ attr_reader :selected, :today, :min, :max, :range_start, :range_end
23
+
24
+ # The closed vocabulary for the mode axis.
25
+ MODES = %i[single range].freeze
26
+ # The closed vocabulary for the caption_layout axis.
27
+ CAPTION_LAYOUTS = %i[label dropdown].freeze
28
+
29
+ # Projected into the registry, llms.txt, and the agent surface.
30
+ AGENT_RULES = [
31
+ "name: makes it a form control (the chosen date posts as an ISO string in a hidden input; " \
32
+ "range mode posts name[start] + name[end]).",
33
+ "month:/selected:/today accept a Date or an ISO string; min:/max: bound the selectable range.",
34
+ "mode: :range selects a span - selected: takes a Date..Date Range, [start, end], or " \
35
+ "{start:, end:}; the second click completes, click-before-start swaps, re-click clears.",
36
+ "The grid is server-rendered - it shows a valid month with no JS; the controller adds " \
37
+ "navigation + selection.",
38
+ "For a text-field + popover, use DatePicker (it composes this) - a bare Calendar is the " \
39
+ "always-visible grid.",
40
+ "caption_layout: :dropdown swaps the month label for month + year selects (jump " \
41
+ "navigation); the year list derives from min:/max: when both are set, else ten years " \
42
+ "around the initial month.",
43
+ "week_numbers: true adds the ISO week column (each row's Thursday decides the number)."
44
+ ].freeze
45
+
46
+ use_stimulus do
47
+ on :root do
48
+ controller :calendar do
49
+ register
50
+ value :month, from: :month_key
51
+ value :selected, from: :selected_iso
52
+ value :mode, "range", if: :range?
53
+ value :range_start, from: :range_start_iso, if: :range?
54
+ value :range_end, from: :range_end_iso, if: :range?
55
+ value :week_start
56
+ value :min, from: :min_iso
57
+ value :max, from: :max_iso
58
+ # Localized month names (I18n) so JS month navigation needs
59
+ # no locale API of its own - app-locale correct everywhere.
60
+ value :month_names, from: :month_names_list
61
+ end
62
+ end
63
+ on :day do
64
+ controller :calendar do
65
+ target :day
66
+ action :select, on: :click
67
+ end
68
+ end
69
+ on :nav_previous do
70
+ controller(:calendar) { action :previousMonth, on: :click }
71
+ end
72
+ on :nav_next do
73
+ controller(:calendar) { action :nextMonth, on: :click }
74
+ end
75
+ # Elements consumed by the template:
76
+ on :dropdown do
77
+ controller(:calendar) { action :jump, on: :change }
78
+ end
79
+ on :grid do
80
+ controller(:calendar) { action :keydown, on: :keydown }
81
+ end
82
+ on :caption do
83
+ controller(:calendar) { target :caption }
84
+ end
85
+ on :start_input do
86
+ controller(:calendar) { target :startInput }
87
+ end
88
+ on :end_input do
89
+ controller(:calendar) { target :endInput }
90
+ end
91
+ on :hidden_input do
92
+ controller(:calendar) { target :input }
93
+ end
94
+ end
95
+
96
+ option :name, :string,
97
+ doc: "Makes the calendar a form control: the pick posts as an ISO string in a hidden input; range " \
98
+ "mode posts name[start] + name[end]."
99
+ option :mode, :symbol, default: :single,
100
+ doc: "Picks one date (:single) or a span (:range). Range selection completes on the " \
101
+ "second click; a click before the start swaps, a re-click clears."
102
+ option :week_start, :integer, default: 0,
103
+ doc: "The first weekday column (0 = Sunday .. 6 = Saturday)."
104
+ option :caption_layout, :symbol, default: :label,
105
+ doc: ":label shows the month text; :dropdown swaps it for month + year " \
106
+ "selects (jump navigation)."
107
+ option :week_numbers, :boolean, default: false,
108
+ doc: "Adds the ISO week-number column (each row's Thursday decides the number)."
109
+
110
+ part "calendar", "Root wrapper - the calendar controller (navigation, selection, roving " \
111
+ "arrow keys) rides here"
112
+ part "calendar-nav", "The header row - previous/next month Buttons around the caption"
113
+ part "calendar-caption", "The month label ('July 2026') - the controller rewrites it on " \
114
+ "navigation from the localized month names; under " \
115
+ "caption_layout: :dropdown it holds the month/year NativeSelect " \
116
+ "pair instead (the controller reflects navigation into them)"
117
+ part "calendar-week-number", "The ISO week column (week_numbers:) - a columnheader stub " \
118
+ "plus one muted rowheader number per week; non-interactive"
119
+ part "calendar-dropdown", "One caption dropdown unit (caption_layout: :dropdown) - the " \
120
+ "visible label with the real <select> stretched invisibly over " \
121
+ "it (the invisible-overlay pattern)",
122
+ states: {
123
+ "data-calendar-unit" => { condition: "always - which unit this select drives",
124
+ values: %w[month year] }
125
+ }
126
+ part "calendar-caption-label", "The visible text + chevron of a caption dropdown " \
127
+ "(aria-hidden - the overlaid select carries the value)"
128
+ part "calendar-dropdown-value", "The label's text span - the controller rewrites it on " \
129
+ "navigation (month name or year)"
130
+ part "calendar-grid", "The role=grid - the weekday header row plus six week rows " \
131
+ "(42 cells, always full weeks)"
132
+ part "calendar-weekdays", "The role=row of weekday column headers"
133
+ part "calendar-weekday", "One role=columnheader two-letter day label"
134
+ part "calendar-week", "One role=row of seven day cells"
135
+ part "calendar-day-cell", "The role=gridcell wrapper - aria-selected lives HERE (the " \
136
+ "ARIA grid contract; it is not valid on the button)"
137
+ part "calendar-day", "One day <button> - the selection vocabulary and the roving tab " \
138
+ "stop ride here",
139
+ states: {
140
+ "data-date" => "always - the day's ISO date (the controller's selection key)",
141
+ "data-selected" => "the day is the single-mode pick, or a start-only range pick " \
142
+ "(bare; a complete range wears the range-* trio instead)",
143
+ "data-range-start" => "the day starts a COMPLETE range",
144
+ "data-range-end" => "the day ends a COMPLETE range",
145
+ "data-range-middle" => "the day sits strictly inside a complete range",
146
+ "data-today" => "the day is today (aria-current=date rides along)",
147
+ "data-outside" => "the day belongs to a neighbouring month (leading/trailing fill)"
148
+ }
149
+ part "calendar-day-label", "The day-number span inside the button"
150
+
151
+ # Parses the date-valued keywords (month:, selected:, min:, max:, today:) -
152
+ # each accepts a Date or an ISO string; selected: in range mode also takes
153
+ # a Date..Date Range, an [start, end] pair, or a {start:, end:} hash.
154
+ # @api private
155
+ def initialize(month: nil, selected: nil, min: nil, max: nil, today: nil, **)
156
+ super(**)
157
+ raise ArgumentError, "unknown mode #{mode.inspect} (one of #{MODES.join(", ")})" unless MODES.include?(mode)
158
+ unless CAPTION_LAYOUTS.include?(caption_layout)
159
+ raise ArgumentError, "unknown caption_layout #{caption_layout.inspect} " \
160
+ "(one of #{CAPTION_LAYOUTS.join(", ")})"
161
+ end
162
+
163
+ if range?
164
+ @range_start, @range_end = parse_range(selected)
165
+ else
166
+ @selected = to_date(selected)
167
+ end
168
+ @today = to_date(today) || Date.today
169
+ @min = to_date(min)
170
+ @max = to_date(max)
171
+ @month = to_date(month) || @selected || @range_start || @today
172
+ end
173
+
174
+ # @api private
175
+ def range? = mode == :range
176
+ # @api private
177
+ def range_complete? = !!(@range_start && @range_end)
178
+
179
+ # Whether the date falls inside the current range selection.
180
+ # @api private
181
+ def in_span?(date)
182
+ return false unless range?
183
+ return date == @range_start unless range_complete?
184
+
185
+ date.between?(@range_start, @range_end)
186
+ end
187
+
188
+ # The 42 cells (6 weeks) for the visible month, leading/trailing
189
+ # days from the neighbours so every week is full.
190
+ # @api private
191
+ def cells
192
+ first = Date.new(@month.year, @month.month, 1)
193
+ lead = (first.wday - week_start) % 7
194
+ start = first - lead
195
+ (0...42).map { |offset| start + offset }
196
+ end
197
+
198
+ # @api private
199
+ # Two-letter column labels from the locale's abbreviated day
200
+ # names, rotated to week_start.
201
+ def weekday_labels
202
+ I18n.t("date.abbr_day_names").rotate(week_start).map { |name| name[0, 2] }
203
+ end
204
+
205
+ # @api private
206
+ def in_month?(date) = date.month == @month.month
207
+ # @api private
208
+ def selected?(date) = @selected && date == @selected
209
+ # @api private
210
+ def today?(date) = date == @today
211
+ # @api private
212
+ def disabled?(date) = (@min && date < @min) || (@max && date > @max)
213
+
214
+ # @api private
215
+ # Mirrors the controller's #captionText exactly (localized month
216
+ # name + year), so server render and client navigation agree in
217
+ # every locale.
218
+ def caption
219
+ "#{month_names_list[@month.month - 1]} #{@month.year}"
220
+ end
221
+
222
+ # @api private
223
+ def dropdown_caption? = caption_layout == :dropdown
224
+
225
+ # @api private
226
+ def month_options
227
+ month_names_list.each_with_index.map { |label, index| [label, index + 1] }
228
+ end
229
+
230
+ # min:/max: pin the year list when both are given; otherwise ten
231
+ # years either side of the initial month (the demo-friendly default;
232
+ # bound it deliberately via min:/max: in real pickers).
233
+ # @api private
234
+ def year_options
235
+ years = @min && @max ? (@min.year..@max.year) : ((@month.year - 10)..(@month.year + 10))
236
+ years.map { |year| [year.to_s, year] }
237
+ end
238
+
239
+ # The ISO week of a displayed row: its Thursday decides (ISO 8601),
240
+ # which stays correct under any week_start.
241
+ # @api private
242
+ def iso_week(week)
243
+ week.find { |date| date.cwday == 4 }.cweek
244
+ end
245
+
246
+ # Exactly one day is the tab stop: the selection (the range start
247
+ # in range mode), else today (in view), else the first enabled day.
248
+ # @api private
249
+ def tab_stop
250
+ anchor = range? ? @range_start : @selected
251
+ @tab_stop ||= (anchor if anchor && in_month?(anchor)) ||
252
+ (@today if in_month?(@today)) ||
253
+ cells.find { |date| in_month?(date) && !disabled?(date) }
254
+ end
255
+
256
+ # @api private
257
+ def root_attributes
258
+ html_attributes.merge_if_not_set(
259
+ { "data-slot" => "calendar", "class" => css }.merge(stimulus_attributes_for(:root))
260
+ .merge(component_data_attributes)
261
+ )
262
+ end
263
+
264
+ # aria-selected lives on the role=gridcell (the ARIA grid contract -
265
+ # it is not a valid attribute on a plain button). Range mode marks
266
+ # the whole span.
267
+ # @api private
268
+ def cell_attributes(date)
269
+ selected = range? ? in_span?(date) : selected?(date)
270
+ {
271
+ "role" => "gridcell", "data-slot" => "calendar-day-cell",
272
+ "class" => css(:day_cell), "aria-selected" => selected.to_s
273
+ }
274
+ end
275
+
276
+ # @api private
277
+ def day_attributes(date)
278
+ iso = date.iso8601
279
+ attrs = {
280
+ "type" => "button", "data-slot" => "calendar-day",
281
+ "data-date" => iso, "class" => css(:day),
282
+ "aria-label" => date.strftime("%B %-d, %Y"),
283
+ "tabindex" => date == tab_stop ? "0" : "-1"
284
+ }.merge(stimulus_attributes_for(:day))
285
+ merge_selection_attributes(attrs, date)
286
+ attrs["data-today"] = "" if today?(date)
287
+ attrs["data-outside"] = "" unless in_month?(date)
288
+ attrs["aria-current"] = "date" if today?(date)
289
+ if disabled?(date)
290
+ attrs["disabled"] = true
291
+ attrs["aria-disabled"] = "true"
292
+ end
293
+ attrs
294
+ end
295
+
296
+ # @api private
297
+ def previous_options
298
+ nav_options("Previous month", :nav_previous)
299
+ end
300
+
301
+ # @api private
302
+ def next_options
303
+ nav_options("Next month", :nav_next)
304
+ end
305
+
306
+ private
307
+
308
+ def nav_options(label, element)
309
+ {
310
+ variant: :ghost, size: :icon, label: label, class: css(:nav_button)
311
+ }.merge(stimulus_attributes_for(element))
312
+ end
313
+
314
+ def to_date(value)
315
+ return if value.nil?
316
+ return value if value.is_a?(Date)
317
+
318
+ Date.parse(value.to_s)
319
+ end
320
+
321
+ # A preselected range: Date..Date, [start, end], or {start:, end:}.
322
+ def parse_range(value)
323
+ case value
324
+ when nil then [nil, nil]
325
+ when Range then [to_date(value.first), to_date(value.last)]
326
+ when Array then [to_date(value[0]), to_date(value[1])]
327
+ when Hash
328
+ pair = value.symbolize_keys
329
+ [to_date(pair[:start]), to_date(pair[:end])]
330
+ else
331
+ [to_date(value), nil] # a single value starts the range
332
+ end
333
+ end
334
+
335
+ # The selection vocabulary per day: a COMPLETE range wears
336
+ # range-start/middle/end; a start-only pick is a plain selected
337
+ # single day; single mode keeps data-selected.
338
+ def merge_selection_attributes(attrs, date)
339
+ if range?
340
+ if range_complete?
341
+ attrs["data-range-start"] = "" if date == @range_start
342
+ attrs["data-range-end"] = "" if date == @range_end
343
+ attrs["data-range-middle"] = "" if date > @range_start && date < @range_end
344
+ elsif @range_start && date == @range_start
345
+ attrs["data-selected"] = ""
346
+ end
347
+ elsif selected?(date)
348
+ attrs["data-selected"] = ""
349
+ end
350
+ end
351
+
352
+ def month_key = @month.strftime("%Y-%m")
353
+ def selected_iso = @selected&.iso8601 || ""
354
+ def range_start_iso = @range_start&.iso8601 || ""
355
+ def range_end_iso = @range_end&.iso8601 || ""
356
+ def min_iso = @min&.iso8601 || ""
357
+ def max_iso = @max&.iso8601 || ""
358
+ def month_names_list = I18n.t("date.month_names").drop(1)
359
+
360
+ private :range?, :range_complete?, :in_span?, :cells, :weekday_labels, :in_month?, :selected?, :today?
361
+ private :disabled?, :caption, :dropdown_caption?, :month_options, :year_options, :iso_week, :tab_stop
362
+ private :root_attributes, :cell_attributes, :day_attributes, :previous_options, :next_options
363
+ end
364
+ end
365
+ end
366
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Calendar
6
+ # The Calendar preview: a fixed month (deterministic screenshots) with
7
+ # a selected day and today, and a range-bounded variant.
8
+ class Preview < Poetry::Core::Preview::Base
9
+ def default
10
+ render_component(month: "2026-06-01", selected: "2026-06-12", today: "2026-06-15",
11
+ class: "rounded-md border")
12
+ end
13
+
14
+ def bounded
15
+ render_component(month: "2026-06-01", today: "2026-06-15",
16
+ min: "2026-06-08", max: "2026-06-22", class: "rounded-md border")
17
+ end
18
+
19
+ def range
20
+ render_component(mode: :range, month: "2026-06-01", today: "2026-06-15",
21
+ selected: Date.new(2026, 6, 9)..Date.new(2026, 6, 18),
22
+ class: "rounded-md border")
23
+ end
24
+
25
+ # caption_layout: :dropdown - the month/year NativeSelect pair;
26
+ # min/max pin the year list (deterministic options).
27
+ def dropdown_caption
28
+ render_component(month: "2026-06-01", selected: "2026-06-12", today: "2026-06-15",
29
+ caption_layout: :dropdown, min: "2024-01-01", max: "2028-12-31",
30
+ class: "rounded-md border")
31
+ end
32
+
33
+ # week_numbers: the ISO week column (June 2026 = weeks 23-28).
34
+ def week_numbers
35
+ render_component(month: "2026-06-01", selected: "2026-06-12", today: "2026-06-15",
36
+ week_numbers: true, class: "rounded-md border")
37
+ end
38
+
39
+ # name: makes the calendar a form participant - the hidden input
40
+ # the controller keeps in sync submits the ISO date.
41
+ def form_participant
42
+ render_component(name: "event[on]", month: "2026-06-01", selected: "2026-06-12",
43
+ today: "2026-06-15", class: "rounded-md border")
44
+ end
45
+
46
+ # Range + name: submits <name>[start] / <name>[end] hidden inputs.
47
+ def form_participant_range
48
+ render_component(mode: :range, name: "stay", month: "2026-06-01", today: "2026-06-15",
49
+ range_start: "2026-06-08", range_end: "2026-06-12",
50
+ class: "rounded-md border")
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Calendar
6
+ # poetry's OWN calendar engine, re-expressed through the cn-*
7
+ # theme layer. The cell-size var + grid geometry stay inline
8
+ # (mechanism); the day-button's whole state chain (selected / today /
9
+ # outside / range tints) rides .cn-calendar-day-button TOGETHER
10
+ # (split-side rule - the tints override each other in-layer). The
11
+ # in-popover bg override stays inline per upstream's own root split.
12
+ class Style < Poetry::Core::Style
13
+ # group/calendar stays dropped (no consumer; the compiled-CSS gate
14
+ # flags markers nothing consumes). It returns with a consumer.
15
+ base "cn-calendar bg-background [--cell-size:--spacing(8)] w-fit " \
16
+ "in-data-[slot=popover-content]:bg-transparent group/calendar " \
17
+ "in-data-[slot=card-content]:bg-transparent"
18
+
19
+ element :nav, "flex items-center justify-between gap-1 pb-2"
20
+ element :nav_button, "size-(--cell-size) p-0 select-none aria-disabled:opacity-50"
21
+ element :caption, "cn-calendar-caption flex-1 text-center select-none"
22
+ # The dropdown caption (caption_layout: :dropdown), upstream's
23
+ # overlay pattern: the visible part is TEXT + a small chevron; the
24
+ # real <select> stretches invisibly over it. Most themes leave the
25
+ # root chromeless (maia/lyra add a border via
26
+ # .cn-calendar-dropdown-root); the focus ring here is poetry's
27
+ # structural a11y floor - an invisible select still needs a
28
+ # visible focus.
29
+ element :caption_dropdowns, "flex flex-1 items-center justify-center gap-1.5 text-sm font-medium"
30
+ # (No cn-calendar-dropdown-root/caption-label names: only maia/lyra
31
+ # theme this surface upstream - the breadcrumb doctrine, a cn name
32
+ # the other themes cannot compile does not ship; maia/lyra restyle
33
+ # via the data-slot hooks instead.)
34
+ element :dropdown_root, "relative rounded-md " \
35
+ "has-[select:focus-visible]:ring-[3px] has-[select:focus-visible]:ring-ring/50"
36
+ element :dropdown_select, "absolute inset-0 opacity-0"
37
+ element :dropdown_label, "flex select-none items-center gap-1 text-sm " \
38
+ "font-medium [&>svg]:size-3.5 [&>svg]:text-muted-foreground"
39
+
40
+ element :grid, "w-full border-collapse"
41
+ element :weekdays, "flex"
42
+ element :weekday, "cn-calendar-weekday flex-1 select-none flex items-center justify-center " \
43
+ "h-(--cell-size)"
44
+ element :week, "mt-2 flex w-full"
45
+ element :day_cell, "relative aspect-square h-full w-full flex-1 p-0 text-center select-none"
46
+
47
+ # The day button: ghost, with the selected/today/outside/range
48
+ # states - the whole treatment in the theme rule.
49
+ element :day, "cn-calendar-day-button relative isolate z-10 flex aspect-square size-auto " \
50
+ "w-full min-w-(--cell-size) items-center justify-center border-0 outline-none " \
51
+ "disabled:pointer-events-none"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,11 @@
1
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
2
+ <% if header? %>
3
+ <div data-slot="card-header" class="<%= css(:header, class: header_class) %>">
4
+ <% if title? %><%= content_tag(title_tag, title, "data-slot" => "card-title", class: css(:title)) %><% end %>
5
+ <% if description? %><div data-slot="card-description" class="<%= css(:description) %>"><%= description %></div><% end %>
6
+ <% if action? %><div data-slot="card-action" class="<%= css(:action) %>"><%= action %></div><% end %>
7
+ </div>
8
+ <% end %>
9
+ <div data-slot="card-content" class="<%= css(:content, class: content_class) %>"><%= content %></div>
10
+ <% if footer? %><%= content_tag(:div, footer_attributes) do %><%= capture(&footer_block) if footer_block %><% end %><% end %>
11
+ <% end %>
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Content surface cards.
6
+ module Card
7
+ # A content surface composed from slots: a header (title,
8
+ # description, and a trailing corner action), the body content
9
+ # block, and a footer row. The header grid gains its trailing
10
+ # column automatically when an action is present, and the title
11
+ # renders as a real heading (h3 by default - set title_tag: to fit
12
+ # the page outline).
13
+ #
14
+ # @example
15
+ # render Poetry::Ui::Card::Component.new do |card|
16
+ # card.with_title { "Team" }
17
+ # card.with_description { "Invite and manage members." }
18
+ # "Body content"
19
+ # end
20
+ class Component < Poetry::Core::Component
21
+ # Projected into the registry, llms.txt, and the agent surface.
22
+ AGENT_RULES = [
23
+ "Compose with the slots (title/description/action/footer) - never rebuild the header grid by hand.",
24
+ "The card body is the content block; use CardAction for the header-corner control.",
25
+ "The title renders as a real heading (h3 default) - set title_tag: to fit the page outline."
26
+ ].freeze
27
+
28
+ renders_one :title, doc: "The heading line, rendered as a real heading element (title_tag:)."
29
+ renders_one :description, doc: "Muted one-liner under the title."
30
+ renders_one :action, doc: "The header's trailing corner control (a button, menu, or link)."
31
+ renders_one :footer,
32
+ doc: "The bottom row (actions/meta). class: merges into the footer div (a border-t divider is " \
33
+ "the canonical use); every other option (id:, data:, ...) rides onto the footer div " \
34
+ "verbatim.",
35
+ renders: lambda { |**options, &block|
36
+ @footer_options = options
37
+ @footer_block = block
38
+ nil
39
+ }
40
+
41
+ option :title_tag, :symbol, default: :h3,
42
+ doc: "The heading element for the title - pick it to fit the page outline."
43
+
44
+ option :content_class, :string,
45
+ doc: "Extra classes merged into the body cell (caller classes win). A chat-in-a-card layout passes " \
46
+ "min-h-0 flex-1 p-0 so the transcript can flex and scroll."
47
+
48
+ option :header_class, :string,
49
+ doc: "Extra classes merged into the header row - border-b rules the title off from the body."
50
+
51
+ validates :title_tag, inclusion: { in: %i[h1 h2 h3 h4 h5 h6] }
52
+
53
+ part "card", "Root container - the vertical flex stack"
54
+ part "card-header", "The title row grid - gains a trailing auto column when " \
55
+ "card-action is present"
56
+ part "card-title", "The heading (title_tag, h3 by default)"
57
+ part "card-description", "Muted one-liner under the title"
58
+ part "card-action", "The header's trailing corner control"
59
+ part "card-content", "The body - the content block renders here"
60
+ part "card-footer", "The bottom row (actions/meta)"
61
+
62
+ # The caller's footer block, captured by with_footer.
63
+ # @api private
64
+ attr_reader :footer_block
65
+
66
+ # @api private
67
+ def footer_attributes
68
+ options = (@footer_options || {}).dup
69
+ { "data-slot" => "card-footer",
70
+ class: css(:footer, class: options.delete(:class)) }.merge(options)
71
+ end
72
+
73
+ # Whether any header slot is set.
74
+ # @api private
75
+ def header?
76
+ title? || description? || action?
77
+ end
78
+
79
+ # @api private
80
+ def root_attributes
81
+ html_attributes.merge_if_not_set(
82
+ { "data-slot" => "card" }.merge(component_data_attributes)
83
+ )
84
+ end
85
+
86
+ private :footer_attributes, :header?, :root_attributes
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Card
6
+ # The Card preview: the title/description default and the full
7
+ # header-action + footer composition.
8
+ class Preview < Poetry::Core::Preview::Base
9
+ def default
10
+ render_component do |card|
11
+ card.with_title { "Revenue" }
12
+ card.with_description { "Last 30 days" }
13
+ "$12,345"
14
+ end
15
+ end
16
+
17
+ def with_action_and_footer
18
+ render_component do |card|
19
+ card.with_title { "Team" }
20
+ card.with_description { "Active members" }
21
+ card.with_action { embed(Poetry::Ui::Button::Component.new(size: :sm, variant: :outline).with_content("Invite")) }
22
+ card.with_footer { "Updated hourly" }
23
+ "24 members"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Card
6
+ # Re-expressed through the cn-* theme layer: grid machinery
7
+ # stays inline (upstream's split), the surface treatment + spacing
8
+ # ride themes/default.css.
9
+ class Style < Poetry::Core::Style
10
+ base "cn-card flex flex-col group/card"
11
+
12
+ element :header, "cn-card-header grid auto-rows-min items-start " \
13
+ "has-data-[slot=card-action]:grid-cols-[1fr_auto] @container/card-header " \
14
+ "group/card-header has-data-[slot=card-description]:grid-rows-[auto_auto]"
15
+ element :title, "cn-card-title"
16
+ element :description, "cn-card-description"
17
+ element :action, "col-start-2 row-span-2 row-start-1 self-start justify-self-end"
18
+ element :content, "cn-card-content"
19
+ element :footer, "cn-card-footer flex items-center"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
2
+ <%= content_tag(:div, viewport_attributes) do %>
3
+ <div class="<%= css(:track, class: [css(vertical? ? :track_vertical : :track_horizontal), track_classes].compact) %>">
4
+ <% slides.each do |slide| %>
5
+ <%= content_tag(:div, capture(&slide.block), item_attributes(slide)) %>
6
+ <% end %>
7
+ </div>
8
+ <% end %>
9
+ <% if show_controls %>
10
+ <%= render Poetry::Ui::Button::Component.new(**control_options(:previous)) do |button| %>
11
+ <% button.with_leading { render Poetry::Ui::Icon::Component.new(name: :"chevron-left") } %>
12
+ <% end %>
13
+ <%= render Poetry::Ui::Button::Component.new(**control_options(:next)) do |button| %>
14
+ <% button.with_leading { render Poetry::Ui::Icon::Component.new(name: :"chevron-right") } %>
15
+ <% end %>
16
+ <% end %>
17
+ <% end %>