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,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Carousel family: the scroll-snap slide strip and its paging controls.
6
+ module Carousel
7
+ # A slide carousel on native scroll-snap: the viewport is a real
8
+ # scroll container, so touch, momentum, and snapping work with no
9
+ # JS, and prev/next paging plus arrow keys are layered on top.
10
+ # Declare slides with with_item; the component owns the region and
11
+ # slide ARIA and renders the controls.
12
+ #
13
+ # Looping, autoplay, and plugins are not supported.
14
+ #
15
+ # @example
16
+ # render Poetry::Ui::Carousel::Component.new(label: "Featured") do |carousel|
17
+ # carousel.with_item { "Slide one" }
18
+ # carousel.with_item { "Slide two" }
19
+ # end
20
+ class Component < Poetry::Core::Component
21
+ # The closed vocabulary for the orientation axis.
22
+ ORIENTATIONS = %i[horizontal vertical].freeze
23
+
24
+ # Projected into the registry, llms.txt, and the agent surface.
25
+ AGENT_RULES = [
26
+ "label: is REQUIRED - the carousel region's accessible name.",
27
+ "Declare slides with with_item - the component stamps the slide roles " \
28
+ "(role=group + aria-roledescription=slide).",
29
+ "Slides are REAL scroll content: they stay reachable by swipe, wheel, and keyboard even " \
30
+ "before JS - never gate content behind the buttons alone.",
31
+ "Size slides with item classes (basis-full default; basis-1/2 lg:basis-1/3 for a gallery).",
32
+ "Change slide spacing as a TRIO: track_classes: \"-ml-1\" plus item classes " \
33
+ "\"pl-1 -scroll-ml-1\" - the gutter padding and its snap scroll-margin move together."
34
+ ].freeze
35
+
36
+ # States statically that with_item requires a content block, so
37
+ # static checks can flag the omission without rendering.
38
+ SLOT_REQUIRED_CONTENT = { item: "the slide" }.freeze
39
+
40
+ # The required slots, stated statically so static checks can flag
41
+ # a missing slide without rendering.
42
+ REQUIRED_SLOTS = { item: "at least one slide" }.freeze
43
+
44
+ renders_many :items,
45
+ doc: "Declares one slide. The content block is required; classes: sizes the slide (basis-full " \
46
+ "default).",
47
+ renders: lambda { |classes: nil, &block|
48
+ raise ArgumentError, "Carousel with_item requires a content block (the slide)" unless block
49
+
50
+ slides << Slide.new(classes: classes, block: block)
51
+ nil
52
+ }
53
+
54
+ use_stimulus do
55
+ on :root do
56
+ controller :carousel do
57
+ register
58
+ value :orientation
59
+ action :keydown, on: :keydown
60
+ end
61
+ end
62
+ on :viewport do
63
+ controller :carousel do
64
+ target :viewport
65
+ action :scrolled, on: :scroll
66
+ end
67
+ end
68
+ # One element per control direction, forwarded into Button
69
+ # kwargs (previously hand-written target + action strings).
70
+ on :previous do
71
+ controller :carousel do
72
+ target :previous
73
+ action :previous, on: :click
74
+ end
75
+ end
76
+ on :next do
77
+ controller :carousel do
78
+ target :next
79
+ action :next, on: :click
80
+ end
81
+ end
82
+ end
83
+
84
+ option :label, :string, required: true,
85
+ doc: "The carousel region's accessible name - required; rendering without it raises."
86
+ option :orientation, :symbol, default: :horizontal,
87
+ doc: "The scroll axis; snapping, controls, and arrow keys follow it."
88
+ option :show_controls, :boolean, default: true,
89
+ doc: "Renders the prev/next buttons; slides stay reachable by swipe, wheel, " \
90
+ "and keyboard without them."
91
+ option :track_classes, :string,
92
+ doc: "Utility classes for the slide track - change spacing as a trio: track_classes: \"-ml-1\" pairs " \
93
+ "with item classes \"pl-1 -scroll-ml-1\"."
94
+
95
+ validates :orientation, inclusion: { in: ORIENTATIONS }
96
+
97
+ part "carousel", "The role=region root - the controller (paging, button state, arrow keys) " \
98
+ "rides here",
99
+ states: {
100
+ "data-orientation" => { condition: "the scroll axis", values: ORIENTATIONS.map(&:to_s) }
101
+ }
102
+ part "carousel-content", "The viewport - a real scroll-snap container (tabindex=0); the " \
103
+ "platform owns the physics"
104
+ part "carousel-item", "One role=group slide - sized by item classes (basis-full default)"
105
+
106
+ # Enforces the required label and at least one slide.
107
+ # @api private
108
+ def before_render
109
+ raise ArgumentError, "Carousel requires label: (the region's accessible name)" if label.blank?
110
+ raise ArgumentError, "Carousel requires at least one with_item" unless items?
111
+ end
112
+
113
+ # The declared slides in render order.
114
+ # @api private
115
+ def slides
116
+ @slides ||= []
117
+ end
118
+
119
+ # Whether the scroll axis is vertical.
120
+ # @api private
121
+ def vertical? = orientation == :vertical
122
+
123
+ # Attributes for the role=region root.
124
+ # @api private
125
+ def root_attributes
126
+ html_attributes.merge_if_not_set(
127
+ {
128
+ "role" => "region", "aria-roledescription" => "carousel", "aria-label" => label,
129
+ "data-slot" => "carousel", "data-orientation" => orientation
130
+ }.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
131
+ )
132
+ end
133
+
134
+ # Attributes for the scroll-snap viewport.
135
+ # @api private
136
+ def viewport_attributes
137
+ {
138
+ "data-slot" => "carousel-content",
139
+ # A REAL scroll region must be keyboard-reachable (the ScrollArea
140
+ # rule); the arrows then page it via the root's keydown.
141
+ "tabindex" => "0",
142
+ "class" => "#{css(:content)} #{css(vertical? ? :content_vertical : :content_horizontal)}"
143
+ }.merge(stimulus_attributes_for(:viewport))
144
+ end
145
+
146
+ # Attributes for one slide.
147
+ # @api private
148
+ def item_attributes(slide)
149
+ # class: rides the merger so caller classes WIN on conflicts -
150
+ # a raw join left basis-1/3 vs the dictionary's basis-full to
151
+ # the compiled sheet's cascade order (which picked basis-full,
152
+ # silently breaking the documented strip sizing).
153
+ {
154
+ "role" => "group", "aria-roledescription" => "slide", "data-slot" => "carousel-item",
155
+ "class" => css(:item, class: [css(vertical? ? :item_vertical : :item_horizontal),
156
+ slide.classes].compact)
157
+ }
158
+ end
159
+
160
+ # Button options for one prev/next control.
161
+ # @api private
162
+ def control_options(direction)
163
+ {
164
+ variant: :outline, size: :"icon-sm",
165
+ label: direction == :previous ? "Previous slide" : "Next slide",
166
+ class: "cn-carousel-#{direction} #{css(:control)} #{css(:"control_#{direction}_#{orientation}")}",
167
+ data: { slot: "carousel-#{direction == :previous ? "previous" : "next"}" }
168
+ }.merge(stimulus_attributes_for(direction))
169
+ end
170
+
171
+ # One declared slide: its extra classes and content block.
172
+ # @api private
173
+ Slide = Data.define(:classes, :block)
174
+
175
+ private :slides, :vertical?, :root_attributes, :viewport_attributes, :item_attributes, :control_options
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Carousel
6
+ # The Carousel preview: full-width slides, a responsive gallery, the
7
+ # spacing trio, and the vertical stack. The -left-12/-right-12
8
+ # controls need gutter room (px-14).
9
+ class Preview < Poetry::Core::Preview::Base
10
+ def default
11
+ render_component(label: "Featured artwork", class: "mx-14 w-64") do |carousel|
12
+ 3.times do |n|
13
+ carousel.with_item do
14
+ tag.div("Slide #{n + 1}",
15
+ class: "flex aspect-square items-center justify-center rounded-md border bg-muted text-2xl")
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ # The Sizes gallery (absorbs the old strip preview): responsive
22
+ # per-item bases, mirroring the docs example - the golden pins the
23
+ # rig-width rendering.
24
+ def sizes
25
+ render_component(label: "Gallery sizes", class: "mx-14 w-96") do |carousel|
26
+ 5.times do |n|
27
+ carousel.with_item(classes: "basis-1/2 lg:basis-1/3") do
28
+ tag.div((n + 1).to_s,
29
+ class: "flex aspect-square items-center justify-center rounded-md border bg-muted text-2xl")
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ # The spacing trio (track_classes + per-item pl/-scroll-ml): the
36
+ # track margin, the gutter padding, and the snap scroll-margin
37
+ # move together - this preview pins the tightened geometry.
38
+ def spacing
39
+ render_component(label: "Tight gallery", track_classes: "-ml-1", class: "mx-14 w-80") do |carousel|
40
+ 5.times do |n|
41
+ carousel.with_item(classes: "basis-1/3 pl-1 -scroll-ml-1") do
42
+ tag.div((n + 1).to_s,
43
+ class: "flex aspect-square items-center justify-center rounded-md border bg-muted text-xl")
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ # Vertical stack, upstream's orientation proportions: fixed-height
50
+ # slides (the auto-height track cannot resolve percentage bases),
51
+ # two visible, enough roster to page. The -top-12/-bottom-12
52
+ # controls need gutter room (my-14).
53
+ def vertical
54
+ render_component(label: "Release milestones", orientation: :vertical,
55
+ class: "mx-auto my-14 grid h-64 w-52") do |carousel|
56
+ 6.times do |n|
57
+ carousel.with_item do
58
+ tag.div("Week #{n + 1}",
59
+ class: "flex h-28 items-center justify-center rounded-md border bg-muted text-sm font-semibold")
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Carousel
6
+ # Style dictionary for the Carousel family, on native scroll-snap:
7
+ # the content wrapper is the REAL scroll container
8
+ # (overflow + snap + hidden scrollbar) rather than an
9
+ # overflow-hidden + transform track; the gutter idiom (-ml-4 track,
10
+ # pl-4 items) and the control positions are kept as designed.
11
+ class Style < Poetry::Core::Style
12
+ base "relative"
13
+
14
+ element :content, "overflow-auto [scrollbar-width:none] snap-mandatory"
15
+ element :content_horizontal, "snap-x"
16
+ element :content_vertical, "snap-y"
17
+
18
+ element :track, "flex"
19
+ element :track_horizontal, "-ml-4"
20
+ element :track_vertical, "-mt-4 flex-col"
21
+
22
+ element :item, "min-w-0 shrink-0 grow-0 basis-full snap-start"
23
+ # The negative scroll-margin cancels the gutter from the snap area:
24
+ # native snap-start (and scrollIntoView) aligns the item's BORDER
25
+ # box, which includes the pl-4 gutter - without it, every slide
26
+ # after the first snaps 16px short and its trailing edge clips.
27
+ # An engine that translates by measured offsets never has this seam.
28
+ element :item_horizontal, "pl-4 -scroll-ml-4"
29
+ element :item_vertical, "pt-4 -scroll-mt-4"
30
+
31
+ element :control, "absolute touch-manipulation"
32
+ element :control_previous_horizontal, "inset-y-0 -left-12 my-auto"
33
+ element :control_previous_vertical, "-top-12 left-1/2 -translate-x-1/2 rotate-90"
34
+ element :control_next_horizontal, "inset-y-0 -right-12 my-auto"
35
+ element :control_next_vertical, "-bottom-12 left-1/2 -translate-x-1/2 rotate-90"
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,22 @@
1
+ <%# Fragment - button + input(s) as SIBLINGS, no wrapper: the source's
2
+ `peer` class contract (labels style via peer-*) breaks if poetry wraps.
3
+ The controller lives on the button; the input resolves by stable id. %>
4
+ <%= content_tag(:button, **root_attributes.to_attributes) do %>
5
+ <span data-slot="checkbox-indicator" <% if indeterminate? %>data-indeterminate=""<% elsif checked? %>data-checked=""<% else %>data-unchecked=""<% end %> aria-hidden="true" class="<%= css(:indicator) %>">
6
+ <%# MinusIcon on indeterminate is a POETRY ADDITION (shadcn renders a
7
+ check for mixed state - a real gap). %>
8
+ <%= render Poetry::Ui::Icon::Component.new(name: indeterminate? ? :minus : :check, class: css(:icon)) %>
9
+ </span>
10
+ <% end %>
11
+ <% if form_participant? %>
12
+ <%# Rails parity hidden FIRST (last-wins ordering, Tags::CheckBox-exact)
13
+ so unchecked SUBMITS unchecked_value; disabled fields submit nothing. %>
14
+ <% if unchecked_value %>
15
+ <%= tag.input(type: "hidden", name: name, value: unchecked_value, disabled: disabled) %>
16
+ <% end %>
17
+ <%# THE form participant + the store: input.indeterminate is a JS-only
18
+ property - the controller derives it from the checked attributes on connect. %>
19
+ <%= tag.input(type: "checkbox", id: input_id, name: name, value: value, checked: checked?,
20
+ disabled: disabled, "data-slot": "checkbox-input", "aria-hidden": true,
21
+ tabindex: "-1", class: css(:input)) %>
22
+ <% end %>
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Checkbox family: the form-participating tri-state toggle.
6
+ module Checkbox
7
+ # A checkbox. A hidden native <input type=checkbox> is the form
8
+ # participant and the source of truth (server-rendered
9
+ # name/value/checked, Rails "1"/"0" plus the unchecked-hidden
10
+ # pair), while the visual button[role=checkbox] only reflects it
11
+ # via aria-checked and the data-checked / data-unchecked /
12
+ # data-indeterminate triple. Toggling flips the input and lets the
13
+ # real change event bubble; a native form reset re-syncs the
14
+ # visual state from the input. checked: is tri-valued - true,
15
+ # false, or :indeterminate (for select-all parents).
16
+ #
17
+ # The button and inputs render as siblings with no wrapper
18
+ # element, so peer-* label styling keyed on the `peer` class works.
19
+ #
20
+ # @example A form checkbox
21
+ # render Poetry::Ui::Checkbox::Component.new(name: "terms", label: "Accept terms")
22
+ class Component < Poetry::Core::Component
23
+ # The closed vocabulary for the checked: axis.
24
+ STATES = [true, false, :indeterminate].freeze
25
+
26
+ # Projected into the registry, llms.txt, and the agent surface.
27
+ AGENT_RULES = [
28
+ "A select-all run rides poetry_checkbox_group (wrapper) + poetry_checkbox_group_all " \
29
+ "(the mixed-state parent) + poetry_checkbox_group_item per member - toggles fan out " \
30
+ "and re-derive automatically.",
31
+ "Use poetry_checkbox (or f.check_box) - never a raw input[type=checkbox] with hand-written " \
32
+ "Tailwind, and never a hand-rolled button[role=checkbox].",
33
+ "Always give it a name: in forms - a checkbox without one submits nothing (visual-only mode " \
34
+ "is for controlled UI like DataTable row selection ONLY).",
35
+ "Every checkbox needs an accessible name: a Label/Field for= association (preferred) or label:.",
36
+ "Indeterminate is set programmatically/server-side only - no user gesture produces it; use it " \
37
+ "for select-all parents.",
38
+ "Select-all recipe: wrap parent + rows in data-controller=\"poetry--core--checkbox-group\" with " \
39
+ "data-action=\"poetry:checkbox:change->poetry--core--checkbox-group#changed\"; mark the parent " \
40
+ "box data: {\"poetry--core--checkbox-group-target\": \"all\"} and each row box target \"item\" - " \
41
+ "the parent fans out, rows re-derive checked/unchecked/indeterminate (DataTable's selectable: " \
42
+ "already does this for its own rows).",
43
+ "Instant-effect settings use Switch; pressed UI tools use Toggle; one-of-N uses RadioGroup.",
44
+ "NEVER write the checked attributes (data-checked/data-unchecked/data-indeterminate) without " \
45
+ "aria-checked and the input's checked property (the controller writes all three; agents " \
46
+ "patching DOM must too).",
47
+ "Don't suppress unchecked_value unless using the array idiom - an unchecked box that submits " \
48
+ "nothing silently keeps the old server value."
49
+ ].freeze
50
+
51
+ use_stimulus do
52
+ on :root do
53
+ controller :checked do
54
+ register
55
+ # No input-id value -> pure visual mode: state lives on the
56
+ # button's checked attributes alone (discouraged; see AGENT_RULES).
57
+ value :input_id, if: :form_participant?
58
+ action :toggle, on: :click
59
+ end
60
+ end
61
+ end
62
+
63
+ # The tri-valued checked: type - casts to exactly
64
+ # true | false | :indeterminate, so one option carries the whole
65
+ # state with no separate indeterminate: flag.
66
+ #
67
+ # @api private
68
+ class CheckedState < ActiveModel::Type::Value
69
+ def type
70
+ :checked_state
71
+ end
72
+
73
+ def cast(value)
74
+ return :indeterminate if value.to_s == "indeterminate"
75
+
76
+ ActiveModel::Type::Boolean.new.cast(value) || false
77
+ end
78
+ end
79
+
80
+ option :checked, CheckedState.new, default: false,
81
+ doc: "The state as ONE tri-valued option (true, false, or :indeterminate) " \
82
+ "- there is no separate indeterminate: flag."
83
+ option :name, :string,
84
+ doc: "Form participation: present renders the hidden native input pair; absent leaves the checkbox " \
85
+ "visual-only (controlled UI)."
86
+ option :value, :string, default: "1", doc: "The value submitted when checked (the Rails check_box \"1\")."
87
+ option :unchecked_value, :string, default: "0",
88
+ doc: "The paired hidden input's value submitted when unchecked; nil " \
89
+ "suppresses the pair (the checkbox-array idiom)."
90
+ option :disabled, :boolean, default: false, doc: "Disables the visual button and the hidden input together."
91
+ option :required, :boolean, default: false,
92
+ doc: "aria-required ONLY, never native required - native required on the hidden " \
93
+ "input would make an unfocusable control invalid."
94
+ option :label, :string, doc: "aria-label fallback when no <label for>/Field association exists."
95
+
96
+ part "checkbox", "The visual button[role=checkbox] - reflects the hidden input via " \
97
+ "aria-checked plus the checked triple",
98
+ states: {
99
+ "data-checked" => "checked (the controller reflects every toggle here, " \
100
+ "aria-checked in step)",
101
+ "data-unchecked" => "unchecked - the indicator goes invisible",
102
+ "data-indeterminate" => "checked: :indeterminate (server/programmatic only; " \
103
+ "the first toggle resolves it to checked)"
104
+ }
105
+ part "checkbox-indicator", "Centering span around the check glyph (minus when " \
106
+ "indeterminate) - CSS-hidden while unchecked, never unmounted",
107
+ states: {
108
+ "data-checked" => "mirrors the control (the controller reflects state on " \
109
+ "every part wearing the triple)",
110
+ "data-unchecked" => "mirrors the control - the indicator is invisible",
111
+ "data-indeterminate" => "mirrors the control - the glyph swaps to minus"
112
+ }
113
+
114
+ # Whether checked: is :indeterminate.
115
+ # @api private
116
+ def indeterminate?
117
+ checked == :indeterminate
118
+ end
119
+
120
+ # Whether checked: is exactly true.
121
+ # @api private
122
+ def checked?
123
+ checked == true
124
+ end
125
+
126
+ # The state word behind the data-* stamp.
127
+ # @api private
128
+ def state
129
+ return "indeterminate" if indeterminate?
130
+
131
+ checked? ? "checked" : "unchecked"
132
+ end
133
+
134
+ # The aria-checked value ("mixed" for indeterminate).
135
+ # @api private
136
+ def aria_checked
137
+ indeterminate? ? "mixed" : checked?.to_s
138
+ end
139
+
140
+ # The label-for target (Field-issued or auto) - server-stable so the
141
+ # sibling input resolves structurally by id, wrapper-free.
142
+ # @api private
143
+ def control_id
144
+ @control_id ||= poetry_instance_id("poetry-checkbox")
145
+ end
146
+
147
+ # The hidden input's id, derived from the control's.
148
+ # @api private
149
+ def input_id
150
+ "#{control_id}-input"
151
+ end
152
+
153
+ # Whether the hidden input pair renders (name: present).
154
+ # @api private
155
+ def form_participant?
156
+ name.present?
157
+ end
158
+
159
+ # Attributes for the visual button[role=checkbox].
160
+ # @api private
161
+ def root_attributes
162
+ attrs = {
163
+ "type" => "button", "role" => "checkbox", "id" => control_id,
164
+ "aria-checked" => aria_checked, "data-#{state}" => "",
165
+ "data-slot" => "checkbox", "disabled" => disabled
166
+ }
167
+ attrs["aria-required"] = true if required
168
+ attrs["aria-label"] = label if label.present?
169
+ html_attributes.merge_if_not_set(
170
+ attrs.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
171
+ )
172
+ end
173
+
174
+ private :indeterminate?, :checked?, :state, :aria_checked, :control_id, :input_id, :form_participant?
175
+ private :root_attributes
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Checkbox
6
+ # The Checkbox preview matrix: all three check-states x disabled x
7
+ # invalid, the Field-bound recipe, and the disabled+checked footgun
8
+ # (disabled fields don't submit - a checked box that submits nothing).
9
+ class Preview < Poetry::Core::Preview::Base
10
+ # @!group States
11
+
12
+ def default
13
+ render_component(name: "terms", label: "Accept terms and conditions")
14
+ end
15
+
16
+ def checked
17
+ render_component(name: "terms", checked: true, label: "Accept terms and conditions")
18
+ end
19
+
20
+ # Server/programmatic only - the select-all parent over a partial
21
+ # selection. Announces as "mixed"; the first toggle resolves to
22
+ # checked (source-exact).
23
+ def indeterminate
24
+ render_component(name: "select_all", checked: :indeterminate, label: "Select all rows")
25
+ end
26
+
27
+ def disabled
28
+ render_component(name: "terms", disabled: true, label: "Accept terms and conditions")
29
+ end
30
+
31
+ # The footgun preview: disabled inputs DON'T submit - a disabled
32
+ # checked checkbox submits nothing, not "1".
33
+ def disabled_checked
34
+ render_component(name: "terms", checked: true, disabled: true, label: "Locked on (submits nothing)")
35
+ end
36
+
37
+ def invalid
38
+ render_component(name: "terms", label: "Accept terms and conditions", "aria-invalid": true)
39
+ end
40
+
41
+ # @!endgroup
42
+
43
+ # @!group Recipes
44
+
45
+ # Field-bound: control_attributes land the id (the label-for
46
+ # target), aria-describedby, and aria-required on the BUTTON.
47
+ def in_a_field
48
+ field = Field::Component.new(
49
+ id: "preview-newsletter", label_text: "Email newsletter",
50
+ hint: "Sent weekly. Unsubscribe anytime.", required: true
51
+ )
52
+ render_component(field) do
53
+ embed(Component.new(name: "newsletter", checked: true,
54
+ **field.control_attributes.transform_keys(&:to_sym)))
55
+ end
56
+ end
57
+
58
+ # Visual-only mode (no name:) - controlled UI like DataTable row
59
+ # selection; state lives on the checked attributes alone (discouraged in forms).
60
+ def visual_only
61
+ render_component(checked: true, label: "Row selected")
62
+ end
63
+
64
+ # @!endgroup
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Checkbox
6
+ # Re-expressed through the cn-* theme layer: the well treatment
7
+ # rides .cn-checkbox in themes/default.css. Poetry mechanisms stay
8
+ # inline: data-unchecked:invisible keeps the indicator in the DOM
9
+ # CSS-hidden (nothing unmounts; there is no exit animation to
10
+ # await), the glyph keeps its designed size-3.5, and :input is the
11
+ # sr-only form store.
12
+ class Style < Poetry::Core::Style
13
+ base "cn-checkbox peer relative shrink-0 outline-none after:absolute " \
14
+ "after:-inset-x-3 after:-inset-y-2 " \
15
+ "disabled:cursor-not-allowed disabled:opacity-50"
16
+
17
+ element :indicator, "cn-checkbox-indicator grid place-content-center text-current transition-none " \
18
+ "data-unchecked:invisible"
19
+
20
+ element :icon, ""
21
+
22
+ # The form participant + the store: visually hidden, out of the
23
+ # accessibility tree (the button is the accessible control).
24
+ element :input, "sr-only"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ <%# InputGroup anatomy: the readonly mono control, trailing copy
2
+ affordance with stacked copy/check glyphs swapping on data-copied. %>
3
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
4
+ <%= content_tag(:div, **Poetry::Core::HTML::Attributes.new(group_attributes).to_attributes) do %>
5
+ <%= tag.input(**input_attributes.to_attributes) %>
6
+ <%= content_tag(:div, **Poetry::Core::HTML::Attributes.new(addon_attributes).to_attributes) do %>
7
+ <%= render copy_button do %>
8
+ <span class="<%= css(:icon_stack) %>">
9
+ <%= render Poetry::Ui::Icon::Component.new(name: :copy, class: css(:icon_copy)) %>
10
+ <%= render Poetry::Ui::Icon::Component.new(name: :check, class: css(:icon_check)) %>
11
+ </span>
12
+ <% end %>
13
+ <% end %>
14
+ <% end %>
15
+ <% end %>