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,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Slider
6
+ # Style dictionary for the Slider family. The geometry spine stays
7
+ # ENTIRELY inline: the --slider-start/--slider-end custom properties
8
+ # (server-rendered, controller-rewritten via CSSOM) and every calc()
9
+ # consumer on :range and the :anchor trio - a swapped theme can
10
+ # restyle the surfaces but can never break the math. The thumb keeps
11
+ # the ring-4 swelling focus ring, and bg-white is literal (the thumb
12
+ # stays white in dark mode).
13
+ class Style < Poetry::Core::Style
14
+ base "cn-slider relative flex w-full touch-none items-center select-none " \
15
+ "data-[disabled]:opacity-50 data-[orientation=vertical]:h-full " \
16
+ "data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col"
17
+
18
+ element :track, "cn-slider-track relative grow overflow-hidden select-none"
19
+
20
+ # The geometry rules: horizontal spans [--slider-start,
21
+ # --slider-end] along the inline axis; vertical grows BOTTOM-up
22
+ # (APG). Logical properties (start-*) flip RTL for free.
23
+ element :range, "cn-slider-range absolute data-[orientation=horizontal]:h-full " \
24
+ "data-[orientation=vertical]:w-full " \
25
+ "data-[orientation=horizontal]:start-(--slider-start) " \
26
+ "data-[orientation=horizontal]:end-[calc(100%-var(--slider-end))] " \
27
+ "data-[orientation=vertical]:bottom-(--slider-start) " \
28
+ "data-[orientation=vertical]:top-[calc(100%-var(--slider-end))] select-none"
29
+
30
+ element :thumb, "cn-slider-thumb block shrink-0 disabled:pointer-events-none disabled:opacity-50 " \
31
+ "select-none"
32
+
33
+ # The per-thumb positioning anchor - an absolutely positioned span
34
+ # riding the geometry vars; calc(P% - 0.5rem) centers the 1rem
35
+ # thumb at P%.
36
+ element :anchor, "absolute data-[orientation=horizontal]:top-1/2 " \
37
+ "data-[orientation=horizontal]:-translate-y-1/2 " \
38
+ "data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:-translate-x-1/2"
39
+
40
+ # The low thumb (range mode) anchors at --slider-start...
41
+ element :anchor_start, "data-[orientation=horizontal]:start-[calc(var(--slider-start)-0.5rem)] " \
42
+ "data-[orientation=vertical]:bottom-[calc(var(--slider-start)-0.5rem)]"
43
+
44
+ # ...the high (or single) thumb at --slider-end.
45
+ element :anchor_end, "data-[orientation=horizontal]:start-[calc(var(--slider-end)-0.5rem)] " \
46
+ "data-[orientation=vertical]:bottom-[calc(var(--slider-end)-0.5rem)]"
47
+
48
+ # Middle thumbs (N-thumb sliders) carry their own inline
49
+ # --slider-mid var - two shared vars cannot place N positions.
50
+ element :anchor_mid, "data-[orientation=horizontal]:start-[calc(var(--slider-mid)-0.5rem)] " \
51
+ "data-[orientation=vertical]:bottom-[calc(var(--slider-mid)-0.5rem)]"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # A spinning loader glyph that announces itself to assistive tech.
6
+ module Spinner
7
+ # A spinning loader glyph that announces itself (role=status +
8
+ # aria-label). Renders the loader-circle glyph as its own svg (so
9
+ # the root carries the spinner identity, not the icon's).
10
+ #
11
+ # @example
12
+ # render Poetry::Ui::Spinner::Component.new(label: "Saving...")
13
+ class Component < Poetry::Core::Component
14
+ # The icon-set glyph the spinner renders.
15
+ GLYPH = :"loader-circle"
16
+ # The svg root's fixed attributes (icon-standard 24px viewBox).
17
+ SVG_BOX = {
18
+ "xmlns" => "http://www.w3.org/2000/svg", "width" => "24", "height" => "24",
19
+ "viewBox" => "0 0 24 24", "fill" => "none", "stroke" => "currentColor",
20
+ "stroke-width" => "2", "stroke-linecap" => "round", "stroke-linejoin" => "round"
21
+ }.freeze
22
+
23
+ # Projected into the registry, llms.txt, and the agent surface.
24
+ AGENT_RULES = [
25
+ "Spinner announces itself (role=status + aria-label) - never a bare spinning div.",
26
+ "Set label: for the loading context ('Saving…'); the default is 'Loading'."
27
+ ].freeze
28
+
29
+ option :label, :string, default: "Loading", doc: "What assistive tech announces - name the loading context."
30
+
31
+ part "spinner", "The spinning <svg> itself (the lucide loader-circle) - announces " \
32
+ "as role=status with aria-label from label:"
33
+
34
+ # @api private
35
+ def call
36
+ content_tag(:svg, glyph, **root_attributes.to_attributes)
37
+ end
38
+
39
+ # @api private
40
+ def root_attributes
41
+ html_attributes.merge_if_not_set(
42
+ SVG_BOX.merge(
43
+ "data-slot" => "spinner", "role" => "status", "aria-label" => label
44
+ ).merge(component_data_attributes)
45
+ )
46
+ end
47
+
48
+ private
49
+
50
+ def glyph
51
+ Poetry::Core::Icons.set(nil).fetch(GLYPH).html_safe
52
+ end
53
+
54
+ private :root_attributes
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Spinner
6
+ # The Spinner preview: the default, a larger one, and a labelled one.
7
+ class Preview < Poetry::Core::Preview::Base
8
+ def default
9
+ render_component
10
+ end
11
+
12
+ def large
13
+ render_component(class: "size-8")
14
+ end
15
+
16
+ def with_context_label
17
+ render_component(label: "Saving your changes")
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Spinner
6
+ # Style dictionary for the Spinner family: the loader glyph, spinning.
7
+ class Style < Poetry::Core::Style
8
+ base "size-4 animate-spin"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
2
+ <div data-slot="stat-label" class="<%= css(:label) %>"><%= label %></div>
3
+ <div class="<%= css(:row) %>">
4
+ <div data-slot="stat-value" class="<%= css(:value) %>"><%= content %></div>
5
+ <% if delta.present? %>
6
+ <span data-slot="stat-delta" data-trend="<%= trend %>" data-sentiment="<%= resolved_sentiment %>" class="<%= delta_classes %>">
7
+ <%= render Poetry::Ui::Icon::Component.new(name: trend_icon) %><%= delta %><span class="sr-only"><%= t("poetry.stat.trend_#{trend}") %></span>
8
+ </span>
9
+ <% end %>
10
+ </div>
11
+ <% if description? %><div data-slot="stat-description" class="<%= css(:description) %>"><%= description %></div><% end %>
12
+ <% if media? %><div data-slot="stat-media" class="<%= css(:media) %>"><%= media %></div><% end %>
13
+ <% end %>
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # One KPI: a labelled value with an optional sentiment-aware delta.
6
+ module Stat
7
+ # One KPI: a muted label over a large tabular-nums value, with an
8
+ # optional sentiment-aware delta, supporting description, and a
9
+ # media slot for a trend visual. Reach for it on dashboards - one
10
+ # Stat per metric, composed in a grid. Styling is utility-only:
11
+ # data-slot names are the restyle seam.
12
+ #
13
+ # @example Revenue KPI with a delta
14
+ # render Poetry::Ui::Stat::Component.new(label: "Revenue", delta: "+12.5%", trend: :up) do
15
+ # "$45,231"
16
+ # end
17
+ class Component < Poetry::Core::Component
18
+ requires_content "the metric value"
19
+
20
+ # The closed vocabulary for the trend axis.
21
+ TRENDS = %i[up down flat].freeze
22
+ # The closed vocabulary for the sentiment axis.
23
+ SENTIMENTS = %i[positive negative neutral].freeze
24
+
25
+ # trend picks the arrow; sentiment defaults FROM the trend (up is
26
+ # good, down is bad) and is overridden when the metric inverts.
27
+ DEFAULT_SENTIMENT = { up: :positive, down: :negative, flat: :neutral }.freeze
28
+ # The arrow glyph each trend renders in the delta pill.
29
+ TREND_ICON = { up: :"trending-up", down: :"trending-down", flat: :minus }.freeze
30
+
31
+ # Projected into the registry, llms.txt, and the agent surface.
32
+ AGENT_RULES = [
33
+ "One Stat is ONE metric: label: names it, the content block is the value - compose " \
34
+ "several in a grid (typically each inside a Card) for a dashboard row.",
35
+ "delta: carries the change text ('+12.5%'); trend: (up/down/flat) sets the arrow and " \
36
+ "the default sentiment. Override sentiment: :positive when DOWN is the good direction " \
37
+ "(costs, churn, error rate) - color follows sentiment, never the arrow.",
38
+ "Keep the value textual - tabular numerals are already applied; units and formatting " \
39
+ "belong in the content ('$45,231', '99.98%').",
40
+ "A Stat is not a chart: a trend over time goes in the media slot (or use poetry-charts)."
41
+ ].freeze
42
+
43
+ renders_one :description, doc: "Muted supporting copy rendered under the value."
44
+ renders_one :media, doc: "The trend-visual slot (sparkline, chart, glyph) below the text stack."
45
+
46
+ option :label, :string, required: true, doc: "The metric's name, shown muted above the value."
47
+ option :delta, :string, doc: "The change text shown in the pill beside the value (\"+12.5%\")."
48
+ option :trend, :symbol, default: :up, doc: "The arrow direction; also derives the default sentiment."
49
+ option :sentiment, :symbol,
50
+ doc: "Overrides the trend-derived sentiment - color follows sentiment, never the arrow (set :positive " \
51
+ "when DOWN is the good direction)."
52
+
53
+ validates :trend, inclusion: { in: TRENDS }
54
+ validates :sentiment, inclusion: { in: SENTIMENTS }, allow_nil: true
55
+
56
+ part "stat", "The stat root - a label/value/delta/description column"
57
+ part "stat-label", "The muted metric name above the value"
58
+ part "stat-value", "The metric itself - large, semibold, tabular numerals"
59
+ part "stat-delta", "The change pill beside the value - arrow icon + delta text with an " \
60
+ "sr-only trend word",
61
+ states: {
62
+ "data-trend" => { condition: "always - the arrow direction",
63
+ values: TRENDS.map(&:to_s) },
64
+ "data-sentiment" => { condition: "always - resolved sentiment (trend-derived " \
65
+ "unless overridden)",
66
+ values: SENTIMENTS.map(&:to_s) }
67
+ }
68
+ part "stat-description", "Muted supporting copy under the value"
69
+ part "stat-media", "The trend-visual slot (sparkline, chart, glyph) below the text stack"
70
+
71
+ # @api private
72
+ def before_render
73
+ ensure_content!
74
+ end
75
+
76
+ # @api private
77
+ def resolved_sentiment
78
+ sentiment || DEFAULT_SENTIMENT.fetch(trend)
79
+ end
80
+
81
+ # @api private
82
+ def delta_classes
83
+ "#{css(:delta)} #{css(:"delta_#{resolved_sentiment}")}"
84
+ end
85
+
86
+ # @api private
87
+ def trend_icon
88
+ TREND_ICON.fetch(trend)
89
+ end
90
+
91
+ # @api private
92
+ def root_attributes
93
+ html_attributes.merge_if_not_set(
94
+ { "data-slot" => "stat" }.merge(component_data_attributes)
95
+ )
96
+ end
97
+
98
+ private :resolved_sentiment, :delta_classes, :trend_icon, :root_attributes
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Stat
6
+ class Preview < Poetry::Core::Preview::Base
7
+ def default
8
+ render_component(label: "Revenue") { "$45,231" }
9
+ end
10
+
11
+ def delta_up
12
+ render_component(label: "Active users", delta: "+12.5%", trend: :up) { "2,847" }
13
+ end
14
+
15
+ def delta_down
16
+ render_component(label: "Conversion rate", delta: "-0.4pt", trend: :down) { "3.1%" }
17
+ end
18
+
19
+ # The inverted metric: DOWN is the good direction, so sentiment
20
+ # overrides the trend-derived color while the arrow stays honest.
21
+ def inverted_down_good
22
+ render_component(label: "Churn", delta: "-0.8%", trend: :down, sentiment: :positive) { "2.1%" }
23
+ end
24
+
25
+ def flat
26
+ render_component(label: "Uptime", delta: "0.00%", trend: :flat) { "99.98%" }
27
+ end
28
+
29
+ def with_description
30
+ render_component(label: "Open invoices", delta: "+4", trend: :up, sentiment: :negative) do |stat|
31
+ stat.with_description { "12 overdue past 30 days" }
32
+ "38"
33
+ end
34
+ end
35
+
36
+ # The media slot: a trend visual under the text stack (a sparkline
37
+ # or chart in a real app; a glyph suffices to pin the anatomy).
38
+ def with_media
39
+ render_component(label: "Deploy frequency", delta: "+18%", trend: :up) do |stat|
40
+ stat.with_media { embed(Poetry::Ui::Icon::Component.new(name: :"trending-up", label: "Trend")) }
41
+ "4.2/day"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Stat
6
+ # Style dictionary for the Stat family - structural utilities on
7
+ # semantic tokens; data-slot selectors remain the always-on restyle
8
+ # seam, and cn names arrive only when a theme wants to own this look.
9
+ class Style < Poetry::Core::Style
10
+ base "flex min-w-0 flex-col gap-1.5"
11
+
12
+ element :label, "text-sm text-muted-foreground"
13
+ element :row, "flex flex-wrap items-baseline gap-x-2 gap-y-1"
14
+ element :value, "text-3xl font-semibold tracking-tight tabular-nums"
15
+ element :delta, "inline-flex items-center gap-1 text-sm font-medium tabular-nums " \
16
+ "[&_svg]:size-3.5 [&_svg]:shrink-0"
17
+ # Theme-owned: the AA-safe success text needs a derived-lightness
18
+ # treatment, and an arbitrary oklch utility in the DICTIONARY
19
+ # would trip the no_raw_colors gate on every consumer page - the
20
+ # design lives in the theme files under this one name.
21
+ element :delta_positive, "cn-stat-delta-positive"
22
+ element :delta_negative, "text-destructive"
23
+ element :delta_neutral, "text-muted-foreground"
24
+ element :description, "text-sm text-muted-foreground"
25
+ element :media, "pt-1"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ <%# Fragment - button + input(s) as SIBLINGS, no wrapper (the peer
2
+ contract; Checkbox-identical). The thumb slide is pure CSS off the
3
+ checked pair; the controller reflects it on every part. %>
4
+ <%= content_tag(:button, **root_attributes.to_attributes) do %>
5
+ <span data-slot="switch-thumb" <% if checked %>data-checked=""<% else %>data-unchecked=""<% end %> aria-hidden="true" class="<%= css(:thumb) %>"></span>
6
+ <% end %>
7
+ <% if form_participant? %>
8
+ <%# Rails parity hidden FIRST (unchecked_value submits when off). %>
9
+ <% if unchecked_value %>
10
+ <%= tag.input(type: "hidden", name: name, value: unchecked_value, disabled: disabled) %>
11
+ <% end %>
12
+ <%= tag.input(type: "checkbox", id: input_id, name: name, value: value, checked: checked,
13
+ disabled: disabled, "data-slot": "switch-input", "aria-hidden": true,
14
+ tabindex: "-1", class: css(:input)) %>
15
+ <% end %>
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Instant-effect on/off controls.
6
+ module Switch
7
+ # An on/off control whose effect applies immediately - flip it and
8
+ # the change happens now, where a Checkbox stages a value for a
9
+ # later submit. Renders a button[role=switch] backed by a hidden
10
+ # native input: give it name: and it participates in form
11
+ # submission and fires a real change event on toggle, so a settings
12
+ # form can auto-submit (e.g. Turbo's
13
+ # data-action: "change->form#requestSubmit").
14
+ #
15
+ # A switch is strictly binary - :indeterminate raises ArgumentError.
16
+ # Give every switch an accessible name via label: or a paired
17
+ # Label/Field.
18
+ #
19
+ # @example A named setting switch
20
+ # render Poetry::Ui::Switch::Component.new(name: "notifications", checked: true,
21
+ # label: "Email notifications")
22
+ class Component < Poetry::Core::Component
23
+ # The closed vocabulary for the size axis.
24
+ SIZES = %i[default sm].freeze
25
+
26
+ # Projected into the registry, llms.txt, and the agent surface.
27
+ AGENT_RULES = [
28
+ "Use poetry_switch - never a styled checkbox pretending to be a switch (role=switch announces " \
29
+ "on/off; that's the point).",
30
+ "Switch = instant effect; Checkbox = staged for submit. If nothing happens until a Save " \
31
+ "button, use Checkbox.",
32
+ "Every switch needs an accessible name (Label/Field for= or label:).",
33
+ "Switches are BINARY - no indeterminate, ever (ArgumentError). A third state means a " \
34
+ "different component.",
35
+ "The instant-effect recipe pairs the flip with server persistence (Turbo auto-submit) - " \
36
+ "never flip UI-only for a setting the user believes is saved.",
37
+ "NEVER write the checked attributes (data-checked/data-unchecked) without aria-checked and " \
38
+ "the input sync (the controller writes all three)."
39
+ ].freeze
40
+
41
+ # Click toggles the hidden input first (firing a real change
42
+ # event), then reflects the new state onto the control.
43
+ use_stimulus do
44
+ on :root do
45
+ controller :checked do
46
+ register
47
+ value :input_id, if: :form_participant?
48
+ action :toggle, on: :click
49
+ end
50
+ end
51
+ end
52
+
53
+ style :size, default: :default, required: true, variants: SIZES,
54
+ doc: "The control's size axis; the thumb scales to match."
55
+
56
+ option :checked, :boolean, default: false, doc: "The server-rendered on/off state."
57
+ option :name, :string, doc: "Names the hidden input, making the switch a form participant."
58
+ option :value, :string, default: "1", doc: "Submitted when the switch is on. Ignored without name:."
59
+ option :unchecked_value, :string, default: "0",
60
+ doc: "Submitted when the switch is off, so the field always posts. Ignored " \
61
+ "without name:."
62
+ option :disabled, :boolean, default: false,
63
+ doc: "Disables the control and its hidden input - a disabled switch neither " \
64
+ "toggles nor submits."
65
+ option :required, :boolean, default: false,
66
+ doc: "Marks the switch required via aria-required (never the native attribute)."
67
+ option :label, :string, doc: "The accessible name, rendered as aria-label - not visible text."
68
+
69
+ part "switch", "The visual button[role=switch] - reflects the hidden input via " \
70
+ "aria-checked plus the checked pair (never indeterminate)",
71
+ states: {
72
+ "data-checked" => "on (the shared checked controller reflects every toggle " \
73
+ "here, aria-checked in step)",
74
+ "data-unchecked" => "off (the server-rendered default)",
75
+ "data-size" => { condition: "always - the resolved size (the thumb reads it " \
76
+ "via group/switch selectors)",
77
+ values: SIZES.map(&:to_s) }
78
+ }
79
+ part "switch-thumb", "The sliding knob - travel is pure CSS off the checked pair",
80
+ states: {
81
+ "data-checked" => "mirrors the control (the controller reflects state on " \
82
+ "every part wearing the pair)",
83
+ "data-unchecked" => "mirrors the control - the thumb sits at the start"
84
+ }
85
+
86
+ # @api private
87
+ def initialize(attributes = {})
88
+ # A switch is strictly binary: aria-checked on role=switch must
89
+ # never be "mixed" - the violation is unrepresentable. Guarded
90
+ # BEFORE the boolean cast would silently truthy it away.
91
+ if attributes.values_at(:checked, "checked").any? { |value| value.to_s == "indeterminate" }
92
+ raise ArgumentError, "Switch is strictly binary - no :indeterminate (use Checkbox for tri-state)"
93
+ end
94
+
95
+ super
96
+ end
97
+
98
+ # @api private
99
+ def state
100
+ checked ? "checked" : "unchecked"
101
+ end
102
+
103
+ # @api private
104
+ def control_id
105
+ @control_id ||= poetry_instance_id("poetry-switch")
106
+ end
107
+
108
+ # @api private
109
+ def input_id
110
+ "#{control_id}-input"
111
+ end
112
+
113
+ # @api private
114
+ def form_participant?
115
+ name.present?
116
+ end
117
+
118
+ # @api private
119
+ def root_attributes
120
+ attrs = {
121
+ "type" => "button", "role" => "switch", "id" => control_id,
122
+ "aria-checked" => checked.to_s, "data-#{state}" => "",
123
+ "data-slot" => "switch", "data-size" => size, "disabled" => disabled
124
+ }
125
+ attrs["aria-required"] = true if required
126
+ attrs["aria-label"] = label if label.present?
127
+ html_attributes.merge_if_not_set(
128
+ attrs.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
129
+ )
130
+ end
131
+
132
+ private :state, :control_id, :input_id, :form_participant?, :root_attributes
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Switch
6
+ # The Switch preview matrix: both sizes x both states x disabled,
7
+ # the Field-bound recipe, and the RTL thumb-travel fix (the poetry
8
+ # addition over the source's LTR-only translate).
9
+ class Preview < Poetry::Core::Preview::Base
10
+ # @!group States
11
+
12
+ def default
13
+ render_component(name: "airplane_mode", label: "Airplane mode")
14
+ end
15
+
16
+ def checked
17
+ render_component(name: "airplane_mode", checked: true, label: "Airplane mode")
18
+ end
19
+
20
+ def disabled
21
+ render_component(name: "airplane_mode", disabled: true, label: "Airplane mode")
22
+ end
23
+
24
+ def disabled_checked
25
+ render_component(name: "airplane_mode", checked: true, disabled: true, label: "Locked on")
26
+ end
27
+
28
+ # @!endgroup
29
+
30
+ # @!group Sizes
31
+
32
+ # size: :sm for dense settings lists - the thumb derives its size
33
+ # via group-data-[size=sm]/switch (no per-element size classes).
34
+ def small
35
+ render_component(name: "compact", size: :sm, checked: true, label: "Compact density")
36
+ end
37
+
38
+ # @!endgroup
39
+
40
+ # @!group Recipes
41
+
42
+ # Field-bound: control_attributes land on the BUTTON (the label-for
43
+ # target).
44
+ def in_a_field
45
+ field = Field::Component.new(
46
+ id: "preview-notifications", label_text: "Notifications",
47
+ hint: "Applied the moment it flips."
48
+ )
49
+ render_component(field) do
50
+ embed(Component.new(name: "notifications", checked: true,
51
+ **field.control_attributes.transform_keys(&:to_sym)))
52
+ end
53
+ end
54
+
55
+ # The RTL travel fix: the knob still moves toward the "on" end
56
+ # (rtl:data-checked:-translate-x-... - a poetry addition;
57
+ # the source's translate is physical/LTR-only).
58
+ def rtl
59
+ render_component(name: "rtl_demo", checked: true, label: "وضع الطيران", dir: "rtl")
60
+ end
61
+
62
+ # @!endgroup
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Switch
6
+ # Re-expressed through the cn-* theme layer. The thumb's travel
7
+ # (data-checked translate + the rtl: fix) lives in the theme rule.
8
+ # The size axis still travels by data attribute; :input stays the
9
+ # sr-only form store.
10
+ class Style < Poetry::Core::Style
11
+ base "cn-switch peer group/switch inline-flex items-center transition-all outline-none " \
12
+ "disabled:cursor-not-allowed disabled:opacity-50 after:-inset-x-3 after:-inset-y-2 " \
13
+ "after:absolute relative"
14
+
15
+ element :thumb, "cn-switch-thumb pointer-events-none block ring-0 transition-transform"
16
+
17
+ # The form participant + the store (the Checkbox architecture).
18
+ element :input, "sr-only"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ <%= content_tag(:div, **container_attributes) do %>
2
+ <%= content_tag(:table, content, **root_attributes.to_attributes) %>
3
+ <% end %>