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,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # DateField family: the segmented date editor over a native input.
6
+ module DateField
7
+ # A segmented date editor: a real native <input type=date> IS the
8
+ # form value - with no JS the native picker works and the value is
9
+ # already ISO - progressively enhanced into per-segment
10
+ # role=spinbutton editing. The locale decides segment order and
11
+ # numerals; arrows cycle values, typed digits accumulate and
12
+ # auto-advance, and blur constrains impossible dates like
13
+ # February 31st. The enhanced input drops out of the tab order but
14
+ # keeps carrying name/required/min/max - native constraint
15
+ # validation stays on.
16
+ #
17
+ # @example
18
+ # render Poetry::Ui::DateField::Component.new(name: "event[on]", label: "Event date")
19
+ class Component < Poetry::Core::Component
20
+ include Poetry::Ui::InputGroupField
21
+
22
+ # Projected into the registry, llms.txt, and the agent surface.
23
+ AGENT_RULES = [
24
+ "Date entry is a DateField (poetry_date_field / form.date_field) - never a masked " \
25
+ "Input, three selects, or a bare input type=date when the design system is in play.",
26
+ "The native input is the form value: params[<name>] is ISO (yyyy-mm-dd) with or " \
27
+ "without JS; min:/max: take Date or ISO strings and ride native validation.",
28
+ "Pair with a Label/Field for the accessible name (label for= the input id); " \
29
+ "standalone use takes label: - segments announce it themselves.",
30
+ "Locale drives segment order and numerals automatically; pass locale: only to pin " \
31
+ "a field to a different locale than the page."
32
+ ].freeze
33
+
34
+ # TimeField subclasses this and EXTENDS the root element with its
35
+ # seconds/hour-cycle values - the declarations-inheritance seam.
36
+ use_stimulus do
37
+ on :root do
38
+ controller :date_field do
39
+ register
40
+ value :locale, if: -> { locale.present? }
41
+ value :placeholder, from: :placeholder_iso
42
+ value :labels, from: :segment_labels_json
43
+ value :placeholders, from: :segment_placeholders_json
44
+ end
45
+ end
46
+ on :group do
47
+ controller :date_field do
48
+ target :group
49
+ action :focusGap, on: :click
50
+ action :settle, on: :focusout
51
+ end
52
+ end
53
+ on :input do
54
+ controller(:date_field) { target :input }
55
+ end
56
+ end
57
+
58
+ option :name, :string, required: true,
59
+ doc: "The form field name - required; the value posts as ISO yyyy-mm-dd with or " \
60
+ "without JS."
61
+ option :value, ActiveModel::Type::Value.new, doc: "Date, or an ISO yyyy-mm-dd string; nil renders empty."
62
+ option :min, ActiveModel::Type::Value.new,
63
+ doc: "The earliest allowed date (Date or ISO string) - rides native constraint validation."
64
+ option :max, ActiveModel::Type::Value.new,
65
+ doc: "The latest allowed date (Date or ISO string) - rides native constraint validation."
66
+ option :required, :boolean, default: false, doc: "Marks the native input required."
67
+ option :disabled, :boolean, default: false, doc: "Disables the field; the segment group dims and goes inert."
68
+ option :readonly, :boolean, default: false, doc: "The value shows but cannot be edited."
69
+ option :invalid, :boolean, default: false, doc: "Paints the destructive border/ring and sets aria-invalid."
70
+ option :id, :string, doc: "The native input's DOM id - the Field label target."
71
+ option :label, :string,
72
+ doc: "Standalone accessible name; inside a form the Field label wires ids instead. Segments announce " \
73
+ "it themselves."
74
+ option :described_by, :string, doc: "Ids for aria-describedby (hint or error text)."
75
+ option :locale, :string, doc: "Pins the field to a locale other than the page's."
76
+ option :placeholder_value, ActiveModel::Type::Value.new,
77
+ doc: "What the first arrow press on an empty segment lands on; defaults to today."
78
+
79
+ part "date-field", "Root - the controller and the enhanced/disabled surface ride here",
80
+ states: {
81
+ "data-enhanced" => "the controller connected and built segments (no JS = the " \
82
+ "native input, visible and styled)",
83
+ "data-disabled" => "disabled: is set",
84
+ "data-invalid" => "invalid: is set (the group wears the destructive ring)"
85
+ }
86
+ # The segments themselves are controller-BUILT (the FileInput list
87
+ # precedent, so they are prose here, not parts): each editable
88
+ # segment is span[data-slot=date-field-segment] with data-type=
89
+ # year|month|day|hour|minute|second|dayPeriod, role=spinbutton
90
+ # (role=textbox on iOS, where VoiceOver cannot focus spinbuttons),
91
+ # and data-placeholder while unfilled; locale separators are
92
+ # span[data-slot=date-field-literal] aria-hidden. Those data-slot
93
+ # names are the restyle seam.
94
+ part "date-field-group", "The bordered segment row (cn-input chrome, focus-within " \
95
+ "ring) - hidden until enhancement, then the editing surface " \
96
+ "the controller fills with segments",
97
+ states: {
98
+ "data-disabled" => "disabled: is set (chrome dims, pointer events off)",
99
+ "data-invalid" => "invalid: is set (destructive border + ring)"
100
+ }
101
+ part "date-field-input", "The native <input type=date> - THE form value in both " \
102
+ "modes; tabindex -1 + aria-hidden once segments exist"
103
+
104
+ # Attributes for the field root.
105
+ # @api private
106
+ def root_attributes
107
+ attrs = {
108
+ "data-slot" => slot_prefix,
109
+ "class" => css
110
+ }.merge(component_data_attributes)
111
+ attrs["data-disabled"] = "" if disabled
112
+ attrs["data-invalid"] = "" if invalid
113
+ html_attributes.merge_if_not_set(attrs.merge(stimulus_attributes_for(:root)))
114
+ end
115
+
116
+ # Attributes for the segment row the controller fills.
117
+ # @api private
118
+ def group_attributes
119
+ attrs = {
120
+ "role" => "presentation",
121
+ "data-slot" => "#{slot_prefix}-group",
122
+ "class" => group_classes
123
+ }
124
+ attrs["aria-label"] = label if label.present?
125
+ attrs["data-invalid"] = "" if invalid
126
+ attrs["data-disabled"] = "" if disabled
127
+ attrs.merge(stimulus_attributes_for(:group))
128
+ end
129
+
130
+ # Attributes for the native input - the form value.
131
+ # @api private
132
+ def input_attributes
133
+ attrs = Poetry::Core::HTML::Attributes.new(
134
+ "type" => input_type,
135
+ "name" => name,
136
+ "id" => control_id,
137
+ "data-slot" => "#{slot_prefix}-input",
138
+ "class" => "#{Input::Style.css} #{css(:input)}"
139
+ )
140
+ attrs["value"] = iso(value) if value.present?
141
+ attrs["min"] = iso(min) if min.present?
142
+ attrs["max"] = iso(max) if max.present?
143
+ attrs["aria-label"] = label if label.present?
144
+ attrs["aria-invalid"] = "true" if invalid && !disabled
145
+ attrs["aria-describedby"] = described_by if described_by.present?
146
+ attrs["required"] = "" if required
147
+ attrs["disabled"] = "" if disabled
148
+ attrs["readonly"] = "" if readonly
149
+ attrs.merge!(stimulus_attributes_for(:input))
150
+ attrs
151
+ end
152
+
153
+ private
154
+
155
+ def input_type
156
+ "date"
157
+ end
158
+
159
+ def slot_prefix
160
+ "date-field"
161
+ end
162
+
163
+ def group_classes
164
+ css(:group)
165
+ end
166
+
167
+ def iso(candidate)
168
+ candidate.respond_to?(:strftime) ? candidate.strftime("%F") : candidate.to_s
169
+ end
170
+
171
+ def placeholder_iso
172
+ placeholder_value.present? ? iso(placeholder_value) : Date.current.strftime("%F")
173
+ end
174
+
175
+ # The AT-facing strings (segment-name fallbacks + the Empty
176
+ # valuetext) and the visual placeholder text per segment - the
177
+ # only translated surface; dates localize themselves via Intl.
178
+ def segment_labels
179
+ {
180
+ empty: t("poetry.date_field.empty"),
181
+ year: t("poetry.date_field.year"),
182
+ month: t("poetry.date_field.month"),
183
+ day: t("poetry.date_field.day"),
184
+ hour: t("poetry.date_field.hour"),
185
+ minute: t("poetry.date_field.minute"),
186
+ second: t("poetry.date_field.second"),
187
+ dayPeriod: t("poetry.date_field.day_period")
188
+ }
189
+ end
190
+
191
+ def segment_placeholders
192
+ {
193
+ year: t("poetry.date_field.placeholder_year"),
194
+ month: t("poetry.date_field.placeholder_month"),
195
+ day: t("poetry.date_field.placeholder_day"),
196
+ hour: t("poetry.date_field.placeholder_time"),
197
+ minute: t("poetry.date_field.placeholder_time"),
198
+ second: t("poetry.date_field.placeholder_time")
199
+ }
200
+ end
201
+
202
+ def segment_labels_json = segment_labels.to_json
203
+ def segment_placeholders_json = segment_placeholders.to_json
204
+
205
+ private :root_attributes, :group_attributes, :input_attributes
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module DateField
6
+ class Preview < Poetry::Core::Preview::Base
7
+ # Empty: placeholder segments (mm/dd/yyyy under en) once enhanced;
8
+ # the styled native input before that.
9
+ def default
10
+ render_component(name: "event[on]", label: "Event date")
11
+ end
12
+
13
+ # A server value fills the segments; the native input carries ISO.
14
+ def with_value
15
+ render_component(name: "invoice[due_on]", value: Date.new(2026, 3, 9), label: "Due date")
16
+ end
17
+
18
+ def with_range
19
+ render_component(name: "booking[night]", label: "First night",
20
+ min: Date.new(2026, 1, 1), max: Date.new(2026, 12, 31))
21
+ end
22
+
23
+ def invalid
24
+ render_component(name: "event[on]", label: "Event date", invalid: true,
25
+ value: Date.new(2020, 2, 29))
26
+ end
27
+
28
+ def disabled
29
+ render_component(name: "event[on]", label: "Event date", disabled: true,
30
+ value: Date.new(2026, 7, 13))
31
+ end
32
+
33
+ # locale: pins segment order and placeholders (dd.mm.yyyy under
34
+ # de-DE) instead of following the page locale.
35
+ def localized
36
+ render_component(name: "geburt[am]", label: "Geburtsdatum", locale: "de-DE",
37
+ value: Date.new(2026, 3, 9))
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module DateField
6
+ # Utility-only (the Separator/Spinner rule): the GROUP wears
7
+ # cn-input's field chrome (zero new theme CSS) with a focus-within
8
+ # ring standing in for the input's focus-visible one, since focus
9
+ # lives on the segments inside. Segments and literals are
10
+ # controller-BUILT, so their look rides descendant variants on the
11
+ # group (the FileInput list idiom). The group stays hidden until the
12
+ # controller stamps data-enhanced on the root; until then the native
13
+ # input is the visible, styled control (no-JS = native pickers).
14
+ class Style < Poetry::Core::Style
15
+ base "group/date-field flex w-full flex-col"
16
+
17
+ element :group,
18
+ "cn-input hidden w-fit min-w-0 select-none items-center tabular-nums " \
19
+ "group-data-[enhanced]/date-field:flex " \
20
+ "focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 " \
21
+ "data-invalid:border-destructive data-invalid:ring-destructive/20 " \
22
+ "dark:data-invalid:ring-destructive/40 " \
23
+ "data-disabled:pointer-events-none data-disabled:opacity-50 " \
24
+ "[&_[data-slot=date-field-segment]]:rounded-sm " \
25
+ "[&_[data-slot=date-field-segment]]:px-0.5 " \
26
+ "[&_[data-slot=date-field-segment]]:outline-none " \
27
+ "[&_[data-slot=date-field-segment]:focus]:bg-accent " \
28
+ "[&_[data-slot=date-field-segment]:focus]:text-accent-foreground " \
29
+ "[&_[data-slot=date-field-segment][data-placeholder]]:text-muted-foreground " \
30
+ "[&_[data-slot=date-field-literal]]:text-muted-foreground"
31
+ element :input, "group-data-[enhanced]/date-field:sr-only"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,23 @@
1
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
2
+ <% if input_variant? %>
3
+ <%= render Poetry::Ui::InputGroup::Component.new do %>
4
+ <%= helpers.poetry_input_group_input(**input_attributes.symbolize_keys) %>
5
+ <%= helpers.poetry_input_group_addon(align: :"inline-end") do %>
6
+ <%= render Poetry::Ui::Popover::Component.new(**popover_options, align: :end) do |popover| %>
7
+ <% popover.with_trigger(**input_trigger_options) do %>
8
+ <%= render Poetry::Ui::Icon::Component.new(name: :calendar) %>
9
+ <% end %>
10
+ <%= render Poetry::Ui::Calendar::Component.new(**calendar_options) %>
11
+ <% end %>
12
+ <% end %>
13
+ <% end %>
14
+ <% else %>
15
+ <%= render Poetry::Ui::Popover::Component.new(**popover_options) do |popover| %>
16
+ <% popover.with_trigger(**trigger_options) do %>
17
+ <%= render Poetry::Ui::Icon::Component.new(name: :calendar) %>
18
+ <span class="min-w-0 truncate" <%= tag.attributes(**label_target_attributes) %>><%= formatted %></span>
19
+ <% end %>
20
+ <%= render Poetry::Ui::Calendar::Component.new(**calendar_options) %>
21
+ <% end %>
22
+ <% end %>
23
+ <% end %>
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module Poetry
6
+ module Ui
7
+ # DatePicker family: a field-shaped trigger opening a calendar popover.
8
+ module DatePicker
9
+ # A date picker: a field-shaped trigger that opens a Calendar in a
10
+ # Popover. Composition, not a new primitive: the Popover owns the
11
+ # overlay, the Calendar owns selection and the form value (name:
12
+ # posts the chosen date as ISO through its hidden input), and a
13
+ # thin glue layer formats the trigger label and closes on pick. A
14
+ # preselected value: renders the formatted label and the chosen day
15
+ # with no JS.
16
+ #
17
+ # @example
18
+ # render Poetry::Ui::DatePicker::Component.new(
19
+ # name: "due_on", label: "Due date", value: Date.new(2026, 6, 5)
20
+ # )
21
+ class Component < Poetry::Core::Component
22
+ # Composition: every DatePicker renders a Popover, whose minted
23
+ # trigger/content ids ride this DOM - a cached or looped
24
+ # DatePicker without identity freezes them, so the stable-identity
25
+ # warning is the point. Forces the registry identity fact past
26
+ # the source derivation (no funnel call in this family itself);
27
+ # popover_options forwards key:/id: to that Popover, so the
28
+ # warning's remedy works through the composition.
29
+ IDENTITY = true
30
+
31
+ # Projected into the registry, llms.txt, and the agent surface.
32
+ AGENT_RULES = [
33
+ "name: is REQUIRED - the chosen date posts as an ISO string (the Calendar's hidden input).",
34
+ "value: preselects a date (a Date or ISO string) - the trigger shows it formatted, no JS needed.",
35
+ "min:/max: bound the selectable range; the label + placeholder are the trigger's text.",
36
+ "key:/id: forwards to the composed Popover - a keyed DatePicker renders cache-stable popover ids.",
37
+ "variant: :input renders a text field with a calendar button - typed parseable dates " \
38
+ "re-select the calendar; single mode only.",
39
+ "For an always-visible grid use Calendar directly - DatePicker is the field+popover form."
40
+ ].freeze
41
+
42
+ use_stimulus do
43
+ on :root do
44
+ controller :date_picker do
45
+ register
46
+ value :placeholder
47
+ value :mode, "range", if: :range?
48
+ action :picked, on: event(:calendar, :change)
49
+ end
50
+ end
51
+ on :label do
52
+ controller(:date_picker) { target :label }
53
+ end
54
+ # The input variant's text field: the picker's input target plus
55
+ # the typed-date sync and ArrowDown-opens actions.
56
+ on :input do
57
+ controller :date_picker do
58
+ target :input
59
+ action :input_changed, on: :input
60
+ action :input_keydown, on: :keydown
61
+ end
62
+ end
63
+ end
64
+
65
+ option :name, :string, required: true, doc: "The form field name - required; the chosen date posts as ISO."
66
+ option :mode, :symbol, default: :single,
67
+ doc: ":single or :range (two-date selection; the trigger shows the joined pair)."
68
+ option :placeholder, :string, default: "Pick a date", doc: "Trigger text while nothing is chosen."
69
+ option :label, :string,
70
+ doc: "The trigger's accessible name (aria-label)."
71
+ option :variant, :symbol, default: :button,
72
+ doc: ":button (the default trigger) or :input - a text field that accepts a typed " \
73
+ "date (parseable text re-selects the calendar) with a calendar icon-button " \
74
+ "opening the popover. Single mode only."
75
+ option :caption_layout, :symbol, default: :label,
76
+ doc: "Forwarded to the wrapped Calendar: :dropdown swaps the caption for " \
77
+ "month + year selects (the date-of-birth recipe - min:/max: bound the " \
78
+ "year list)."
79
+
80
+ # ONE owned part: DatePicker is composition - the Popover owns the
81
+ # overlay, the Calendar owns the grid + the form value, the trigger
82
+ # Button owns data-slot=date-picker-trigger (each under its own
83
+ # data-component root, so each declares its own contract).
84
+ part "date-picker", "Root wrapper - the glue controller (formats the trigger label, " \
85
+ "closes on pick) around the composed Popover + Calendar"
86
+
87
+ # Parses the date collaborators (value/min/max/month) up front.
88
+ # @api private
89
+ def initialize(value: nil, min: nil, max: nil, month: nil, **)
90
+ super(**)
91
+ if range?
92
+ @range_start, @range_end = parse_range(value)
93
+ else
94
+ @value = to_date(value)
95
+ end
96
+ @min = to_date(min)
97
+ @max = to_date(max)
98
+ @month = to_date(month)
99
+ end
100
+
101
+ # Enforces the name/variant/mode contracts.
102
+ # @api private
103
+ def before_render
104
+ raise ArgumentError, "DatePicker requires name: (the form field)" if name.blank?
105
+ raise ArgumentError, "DatePicker variant: must be :button or :input" unless %i[button input].include?(variant)
106
+ raise ArgumentError, "DatePicker variant: :input is single-mode only" if input_variant? && range?
107
+ end
108
+
109
+ # The parsed date collaborators for the template.
110
+ # @api private
111
+ attr_reader :value, :min, :max, :month, :range_start, :range_end
112
+
113
+ # Whether mode: is :range.
114
+ # @api private
115
+ def range? = mode == :range
116
+
117
+ # Whether variant: is :input.
118
+ # @api private
119
+ def input_variant? = variant == :input
120
+
121
+ # Range mode joins the pair with SHORT month names ("Jun 5, 2026 -
122
+ # Jun 12, 2026" - two long-month dates outgrow any reasonable
123
+ # trigger); a start-only value shows one date.
124
+ # @api private
125
+ def formatted
126
+ if range?
127
+ return placeholder unless @range_start
128
+
129
+ [@range_start, @range_end].compact.map { |date| date.strftime("%b %-d, %Y") }.join(" – ")
130
+ else
131
+ @value ? @value.strftime("%B %-d, %Y") : placeholder
132
+ end
133
+ end
134
+
135
+ # Options forwarded to the wrapped Calendar.
136
+ # @api private
137
+ def calendar_options
138
+ selected = range? ? [@range_start, @range_end].compact.presence : @value
139
+ { name: name, mode: (:range if range?), selected: selected,
140
+ caption_layout: (caption_layout unless caption_layout == :label),
141
+ min: @min, max: @max, month: @month || @value || @range_start }.compact
142
+ end
143
+
144
+ # Attributes for the root wrapper.
145
+ # @api private
146
+ def root_attributes
147
+ html_attributes.merge_if_not_set(
148
+ { "data-slot" => "date-picker", "class" => css }
149
+ .merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
150
+ )
151
+ end
152
+
153
+ # Button options for the field-shaped trigger.
154
+ # @api private
155
+ def trigger_options
156
+ {
157
+ variant: :outline,
158
+ label: label.presence,
159
+ class: css(range? ? :trigger_range : :trigger_single,
160
+ class: [css(:trigger_chrome),
161
+ (css(:trigger_placeholder) unless value)].compact.join(" ")),
162
+ data: { slot: "date-picker-trigger" }
163
+ }.compact
164
+ end
165
+
166
+ # Wiring for the trigger-label span the controller rewrites.
167
+ # @api private
168
+ def label_target_attributes
169
+ stimulus_attributes_for(:label)
170
+ end
171
+
172
+ # The input variant's text field: the picker's input target plus the
173
+ # typed-date sync and ArrowDown-opens actions. It carries NO name -
174
+ # the calendar's hidden ISO input stays THE form value.
175
+ # @api private
176
+ def input_attributes
177
+ stimulus_attributes_for(:input).merge(
178
+ "value" => (formatted if value), "placeholder" => placeholder,
179
+ "aria-label" => label.presence || "Date"
180
+ ).compact
181
+ end
182
+
183
+ # The addon's icon button IS the popover trigger - InputGroup's own
184
+ # icon-xs chrome on Popover's wired Button.
185
+ # @api private
186
+ def input_trigger_options
187
+ style = Poetry::Ui::InputGroup::Style
188
+ {
189
+ variant: :ghost, label: label.presence || "Choose date",
190
+ class: [style.css(:button), style.css(:button_icon_xs)].join(" "),
191
+ "data-size": "icon-xs", data: { slot: "date-picker-trigger" }
192
+ }
193
+ end
194
+
195
+ # Options for the composed Popover. Identity forwards: a keyed
196
+ # (or explicitly-id'd) DatePicker keys the Popover too, so its
197
+ # minted trigger/content ids are render-stable - the
198
+ # stable-identity warning's remedy works through the composition.
199
+ # @api private
200
+ def popover_options
201
+ { label: label.presence || "Choose date", content_class: "w-auto p-0",
202
+ key: popover_key }.compact
203
+ end
204
+
205
+ # The forwarded identity: the caller's key:, or the explicit root
206
+ # id (render-stable already; the Popover namespaces its own ids
207
+ # from it, so no duplicate).
208
+ # @api private
209
+ def popover_key
210
+ stable_key || html_attributes["id"].presence
211
+ end
212
+
213
+ private
214
+
215
+ def to_date(value)
216
+ return if value.nil?
217
+ return value if value.is_a?(Date)
218
+
219
+ Date.parse(value.to_s)
220
+ end
221
+
222
+ # A preselected range: Date..Date, [start, end], or {start:, end:}.
223
+ def parse_range(value)
224
+ case value
225
+ when nil then [nil, nil]
226
+ when Range then [to_date(value.first), to_date(value.last)]
227
+ when Array then [to_date(value[0]), to_date(value[1])]
228
+ when Hash
229
+ pair = value.symbolize_keys
230
+ [to_date(pair[:start]), to_date(pair[:end])]
231
+ else
232
+ [to_date(value), nil]
233
+ end
234
+ end
235
+
236
+ private :range?, :input_variant?, :formatted, :calendar_options, :root_attributes, :trigger_options
237
+ private :label_target_attributes, :input_attributes, :input_trigger_options
238
+ private :popover_options, :popover_key
239
+ end
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module DatePicker
6
+ # The DatePicker preview: the empty (placeholder) trigger and a
7
+ # preselected one (server-formatted, no JS).
8
+ class Preview < Poetry::Core::Preview::Base
9
+ def default
10
+ render_component(name: "due_on", label: "Due date")
11
+ end
12
+
13
+ def preselected
14
+ render_component(name: "due_on", label: "Due date", value: "2026-06-12", month: "2026-06-01")
15
+ end
16
+
17
+ def range
18
+ render_component(name: "stay", mode: :range, label: "Stay dates",
19
+ placeholder: "Pick a date range",
20
+ value: %w[2026-06-09 2026-06-18], month: "2026-06-01")
21
+ end
22
+
23
+ def input
24
+ render_component(name: "subscription_on", variant: :input, label: "Subscription date",
25
+ placeholder: "June 1, 2026", value: "2026-06-01", month: "2026-06-01",
26
+ class: "w-64")
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module DatePicker
6
+ # The DatePicker is composition (Popover + Calendar); it owns no
7
+ # visual of its own beyond the trigger's field shape, which the
8
+ # component sets inline on the Button. This dictionary exists so the
9
+ # component participates in the registry/verify machinery; the root is
10
+ # an inline-block wrapper.
11
+ class Style < Poetry::Core::Style
12
+ base "inline-block"
13
+
14
+ # The trigger Button's classes (dictionary-resident so safelists
15
+ # ship them - a bare Ruby string compiles in no host). Range
16
+ # triggers run wider (two dates + the dash; upstream sizes its
17
+ # range demo up the same way).
18
+ element :trigger_single, "w-56"
19
+ element :trigger_range, "w-72"
20
+ element :trigger_chrome, "justify-start font-normal"
21
+ element :trigger_placeholder, "text-muted-foreground"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ <%= content_tag(:"turbo-frame", **root_attributes.to_attributes) do %>
2
+ <div <%= tag.attributes(**stimulus_attributes_for(:placeholder)) %>>
3
+ <%= content.presence || render(Poetry::Ui::Skeleton::Component.new(class: "h-24 w-full")) %>
4
+ </div>
5
+ <template <%= tag.attributes(**stimulus_attributes_for(:error_template)) %>>
6
+ <div data-slot="deferred-error" class="flex flex-col items-start gap-2">
7
+ <p class="text-sm text-destructive"><%= t("poetry.deferred.failed") %></p>
8
+ <%= render(Poetry::Ui::Button::Component.new(
9
+ variant: :outline, size: :sm,
10
+ **stimulus_attributes_for(:retry)
11
+ )) do %>
12
+ <%= t("poetry.deferred.retry") %>
13
+ <% end %>
14
+ </div>
15
+ </template>
16
+ <% end %>