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,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Semantic data tables composed from part helpers.
6
+ module Table
7
+ # A semantic <table> wrapped in a horizontally scrolling container.
8
+ # Compose the sections with the part helpers (poetry_table_header /
9
+ # _body / _row / _head / _cell / _footer / _caption), which render
10
+ # real thead/tbody/tr/th/td elements carrying the family's styling.
11
+ # Static and JS-free by itself - DataTable adds sorting, filtering,
12
+ # and selection on top.
13
+ #
14
+ # sticky_header: true pins the <thead> while the container scrolls.
15
+ # It only takes effect once container_class: caps the height (e.g.
16
+ # "max-h-96"), and it requires scroll_label: - the container becomes
17
+ # a focusable scroll region, which needs an accessible name.
18
+ #
19
+ # @example
20
+ # render Poetry::Ui::Table::Component.new do
21
+ # safe_join([
22
+ # poetry_table_header { poetry_table_row { poetry_table_head { "Invoice" } } },
23
+ # poetry_table_body { poetry_table_row { poetry_table_cell { "INV001" } } }
24
+ # ])
25
+ # end
26
+ class Component < Poetry::Core::Component
27
+ # Projected into the registry, llms.txt, and the agent surface.
28
+ AGENT_RULES = [
29
+ "Compose the table with the part helpers (poetry_table_header/_body/_row/_head/_cell) - " \
30
+ "they carry the data-slot + classes onto real thead/tbody/tr/th/td.",
31
+ "A column header is poetry_table_head (a <th>); a data cell is poetry_table_cell (a <td>).",
32
+ "Mark a selected row with data-selected on poetry_table_row - never a bespoke highlight class.",
33
+ "sticky_header: true pins the thead while the container scrolls - it only scrolls once " \
34
+ "container_class: caps the height (\"max-h-96\"); without a cap nothing sticks.",
35
+ "sticky_header requires scroll_label: - the container becomes a focusable scroll region " \
36
+ "(tabindex=0 + role=region) and a keyboard-reachable region needs a name (the ScrollArea rule)."
37
+ ].freeze
38
+
39
+ option :sticky_header, :boolean, default: false,
40
+ doc: "Pins the <thead> while the container scrolls; needs a height cap " \
41
+ "(container_class:) to take effect, and requires scroll_label:."
42
+ option :container_class, :string,
43
+ doc: "Extra classes for the scroll container - e.g. \"max-h-96\" to cap its height."
44
+ option :scroll_label, :string,
45
+ doc: "The scroll region's accessible name, required with sticky_header: a scrollable region a " \
46
+ "keyboard can't reach fails WCAG (axe scrollable-region-focusable), and a focusable region needs " \
47
+ "a name."
48
+
49
+ part "table", "The semantic <table> element itself - the root the part helpers compose into"
50
+ part "table-caption", "The <caption> (poetry_table_caption) - the table's accessible purpose"
51
+ part "table-header", "The <thead> (poetry_table_header) holding the column-header row"
52
+ part "table-body", "The <tbody> (poetry_table_body) holding the data rows"
53
+ part "table-footer", "The <tfoot> (poetry_table_footer) - totals/summary rows"
54
+ part "table-row", "A <tr> (poetry_table_row) in any section",
55
+ states: {
56
+ "data-selected" => "the row is marked selected (presence attribute, no value) - " \
57
+ "the theme tints it"
58
+ }
59
+ part "table-head", "A column header <th> (poetry_table_head)"
60
+ part "table-cell", "A data <td> (poetry_table_cell)"
61
+
62
+ # No content = an empty <table> in a scroll container.
63
+ requires_content "the table sections (poetry_table_* helpers)"
64
+
65
+ # @api private
66
+ def before_render
67
+ ensure_content!
68
+ return unless sticky_header && scroll_label.blank?
69
+
70
+ raise ArgumentError,
71
+ "Table sticky_header: requires scroll_label: (the scroll region's accessible name)"
72
+ end
73
+
74
+ # @api private
75
+ def container_attributes
76
+ extra = [(css(:container_sticky) if sticky_header), container_class].compact.join(" ")
77
+ attrs = { "data-slot" => "table-container", "class" => css(:container, class: extra.presence) }
78
+ attrs.merge!("tabindex" => "0", "role" => "region", "aria-label" => scroll_label) if sticky_header
79
+ attrs
80
+ end
81
+
82
+ # @api private
83
+ def root_attributes
84
+ html_attributes.merge_if_not_set(
85
+ { "data-slot" => "table" }.merge(component_data_attributes)
86
+ )
87
+ end
88
+
89
+ private :container_attributes, :root_attributes
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Table
6
+ # The Table preview: the ported table-demo (recent invoices) with a
7
+ # caption, a footer total, and a selected row. Built with the same
8
+ # data-slot + Style parts the poetry_table_* helpers stamp (the
9
+ # helpers themselves are covered by the components-helper drift test).
10
+ class Preview < Poetry::Core::Preview::Base
11
+ ROWS = [
12
+ ["INV001", "Paid", "$250.00", true],
13
+ ["INV002", "Pending", "$150.00", false],
14
+ ["INV003", "Unpaid", "$350.00", false],
15
+ ["INV004", "Paid", "$500.00", false]
16
+ ].freeze
17
+
18
+ STATUSES = %w[Paid Pending Unpaid].freeze
19
+
20
+ def default
21
+ render_component do
22
+ safe_join([caption, header, body, footer])
23
+ end
24
+ end
25
+
26
+ # sticky_header pins the thead while the capped container scrolls
27
+ # - enough rows to overflow the max-h-56 cap.
28
+ def sticky
29
+ render_component(sticky_header: true, container_class: "max-h-56",
30
+ scroll_label: "Recent invoices") do
31
+ safe_join([header, long_body])
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def caption
38
+ part(:caption, :caption, "table-caption") { "A list of your recent invoices." }
39
+ end
40
+
41
+ def header
42
+ part(:thead, :header, "table-header") do
43
+ row do
44
+ safe_join([head { "Invoice" }, head { "Status" }, head(class: "text-right") { "Amount" }])
45
+ end
46
+ end
47
+ end
48
+
49
+ def body
50
+ part(:tbody, :body, "table-body") do
51
+ safe_join(ROWS.map do |invoice, status, amount, selected|
52
+ row(data: selected ? { slot: "table-row", selected: "" } : { slot: "table-row" }) do
53
+ safe_join([
54
+ cell(class: "font-medium") { invoice },
55
+ cell { status },
56
+ cell(class: "text-right") { amount }
57
+ ])
58
+ end
59
+ end)
60
+ end
61
+ end
62
+
63
+ def footer
64
+ part(:tfoot, :footer, "table-footer") do
65
+ row { safe_join([cell(colspan: 2) { "Total" }, cell(class: "text-right") { "$1,250.00" }]) }
66
+ end
67
+ end
68
+
69
+ def long_body
70
+ part(:tbody, :body, "table-body") do
71
+ safe_join((1..12).map do |i|
72
+ row do
73
+ safe_join([
74
+ cell(class: "font-medium") { format("INV%03d", i) },
75
+ cell { STATUSES[i % 3] },
76
+ cell(class: "text-right") { format("$%d50.00", i) }
77
+ ])
78
+ end
79
+ end)
80
+ end
81
+ end
82
+
83
+ # tag.<el> with the Style class + data-slot - what the helper emits.
84
+ def part(tag_name, element, slot, **attrs, &)
85
+ classes = [Style.css(element), attrs.delete(:class)].compact.join(" ")
86
+ data = attrs.delete(:data) || { slot: slot }
87
+ content_tag(tag_name, capture(&), **attrs, class: classes, data: data)
88
+ end
89
+
90
+ def row(**attrs, &) = part(:tr, :row, "table-row", **attrs, &)
91
+ def head(**attrs, &) = part(:th, :head, "table-head", **attrs, &)
92
+ def cell(**attrs, &) = part(:td, :cell, "table-cell", **attrs, &)
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Table
6
+ # Re-expressed through the cn-* theme layer. Zero JS - the row's
7
+ # selected state travels as data-selected, its tint in the theme
8
+ # with every other row tint. The scroll container carries its own
9
+ # themed hook (cn-table-container).
10
+ class Style < Poetry::Core::Style
11
+ base "cn-table"
12
+
13
+ element :container, "cn-table-container"
14
+ element :container_sticky,
15
+ "overflow-y-auto [&_thead]:sticky [&_thead]:top-0 [&_thead]:z-10 [&_thead]:bg-background"
16
+ element :header, "cn-table-header"
17
+ element :body, "cn-table-body"
18
+ element :footer, "cn-table-footer"
19
+ element :row, "cn-table-row transition-colors has-aria-expanded:bg-muted/50"
20
+ element :head, "cn-table-head"
21
+ element :cell, "cn-table-cell"
22
+ element :caption, "cn-table-caption"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,10 @@
1
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
2
+ <%= content_tag(:div, list_attributes) do %>
3
+ <% tab_defs.each do |tab| %>
4
+ <%= content_tag(:button, tab.title, trigger_attributes(tab)) %>
5
+ <% end %>
6
+ <% end %>
7
+ <% tab_defs.reject(&:panel_less).each do |tab| %>
8
+ <%= content_tag(:div, panel_body(tab), panel_attributes(tab)) %>
9
+ <% end %>
10
+ <% end %>
@@ -0,0 +1,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Tabbed views switched by a tablist.
6
+ module Tabs
7
+ # A tablist of triggers that switches between panels. Declare each
8
+ # tab with with_tab (title plus a panel block) and the component
9
+ # wires the tab/tablist/tabpanel roles, their ids, and the keyboard:
10
+ # arrow keys move focus along the tablist and activate the focused
11
+ # tab. The active panel is server-rendered visible, so the initial
12
+ # view needs no JS.
13
+ #
14
+ # default: picks the active tab by value (otherwise the first
15
+ # enabled tab wins); label: names the tablist for assistive tech -
16
+ # recommended when a page has several tab sets.
17
+ #
18
+ # @example
19
+ # <%= poetry_tabs(default: "account", label: "Account settings") do |tabs| %>
20
+ # <% tabs.with_tab("Account", value: "account") do %>...panel...<% end %>
21
+ # <% tabs.with_tab("Password", value: "password") do %>...panel...<% end %>
22
+ # <% end %>
23
+ class Component < Poetry::Core::Component
24
+ # The closed vocabulary for the orientation axis.
25
+ ORIENTATIONS = %i[horizontal vertical].freeze
26
+ # The closed vocabulary for the list-variant axis.
27
+ VARIANTS = %i[default line].freeze
28
+
29
+ # Projected into the registry, llms.txt, and the agent surface.
30
+ AGENT_RULES = [
31
+ "Declare tabs with with_tab(title, value:) + the panel block (or defer: for a " \
32
+ "lazy turbo-frame panel) - never hand-wire role=tab/tabpanel ids.",
33
+ "panel: false declares a list-only tab (no tabpanel renders, the trigger drops " \
34
+ "aria-controls) - for demos/pattern shells; real tab sets carry panels.",
35
+ "default: picks the server-rendered active tab (the first enabled tab otherwise) - the " \
36
+ "panel is visible without JS.",
37
+ "label: names the tablist (aria-label) - recommended whenever the page has several tab sets.",
38
+ "Tabs switch VIEWS of one context; use navigation (links) when the URL should change."
39
+ ].freeze
40
+
41
+ # The same facts the before_render raise enforces, stated
42
+ # statically: poetry check flags the omission without rendering.
43
+ REQUIRED_SLOTS = { tab: "at least one tab" }.freeze
44
+
45
+ renders_many :tabs,
46
+ doc: "Declares one tab: the title, its value:, and the panel as the block (defer: swaps in a " \
47
+ "lazy turbo-frame panel; panel: false declares a list-only tab). Omitting all three " \
48
+ "raises.",
49
+ renders: lambda { |title, value:, disabled: false, defer: nil, panel: true, &block|
50
+ unless block || defer || panel == false
51
+ raise ArgumentError,
52
+ "Tabs tab #{title.inspect} requires a panel block, defer:, or panel: false"
53
+ end
54
+
55
+ tab_defs << Tab.new(title: title, value: value.to_s, disabled: disabled, panel: block,
56
+ defer: defer, panel_less: panel == false)
57
+ nil
58
+ }
59
+
60
+ use_stimulus do
61
+ on :root do
62
+ controller(:tabs) { register }
63
+ end
64
+ # Both controllers declare on one element: roving-focus registers
65
+ # and owns the keyboard; the tabs controller is NOT registered on
66
+ # the list - its focus_activate action routes up to the root
67
+ # instance.
68
+ on :list do
69
+ controller :roving_focus do
70
+ register
71
+ value :orientation
72
+ value :loop, true
73
+ action :keydown, on: :keydown
74
+ end
75
+ controller :tabs do
76
+ action :focus_activate, on: event(:roving_focus, :entry)
77
+ end
78
+ end
79
+ on :trigger do
80
+ controller(:tabs) { action :activate, on: :click }
81
+ end
82
+ end
83
+
84
+ # The operate surface: activate a tab by its value (registered per
85
+ # instance, opt-in - see poetry-agent).
86
+ tool :set_value,
87
+ description: "Activate the tab whose value matches and show its panel.",
88
+ params: { value: { type: "string", required: true, description: "The tab's value." } },
89
+ executes: %i[tabs set_value],
90
+ mutating: true
91
+
92
+ # The rendered instance knows its tab values: the payload's schema
93
+ # carries them as the enum, so an agent can only ask for a tab that
94
+ # exists (the class-level projection stays a plain string).
95
+ def webmcp_tool_definition(definition)
96
+ return definition unless definition["name"] == "set_value"
97
+
98
+ values = tab_defs.map(&:value)
99
+ return definition if values.empty?
100
+
101
+ schema = definition.fetch("inputSchema")
102
+ value = schema.fetch("properties").fetch("value").merge("enum" => values)
103
+ definition.merge("inputSchema" => schema.merge("properties" => schema["properties"].merge("value" => value)))
104
+ end
105
+
106
+ option :default, :string,
107
+ doc: "The value of the server-rendered active tab; defaults to the first enabled tab. Raises when it " \
108
+ "matches no tab."
109
+ option :label, :string, doc: "The tablist's accessible name - recommended when a page has several tab sets."
110
+ option :orientation, :symbol, default: :horizontal,
111
+ doc: "The tab axis; :vertical stacks the triggers and flips the arrow keys."
112
+ option :variant, :symbol, default: :default,
113
+ doc: "The list's visual treatment: :default a filled capsule, :line an underline " \
114
+ "indicator."
115
+
116
+ validates :orientation, inclusion: { in: ORIENTATIONS }
117
+ validates :variant, inclusion: { in: VARIANTS }
118
+
119
+ part "tabs", "Root wrapper - the orientation rides here and flips the flex direction",
120
+ states: {
121
+ "data-orientation" => { condition: "the tab axis (matches aria-orientation on the list)",
122
+ values: %w[horizontal vertical] }
123
+ }
124
+ part "tabs-list", "The role=tablist row of triggers - the roving-focus keyboard group and " \
125
+ "the visual variant ride here",
126
+ states: {
127
+ "data-variant" => "the list treatment - default (filled capsule) or line (underline indicator)"
128
+ }
129
+ part "tabs-trigger", "One role=tab button per tab",
130
+ states: {
131
+ "data-active" => "the selected tab (the controller moves it with aria-selected on activation)",
132
+ "data-disabled" => "tab is disabled - also filters it from the roving-focus collection",
133
+ "data-value" => "the tab's value - the key the controller matches panels against"
134
+ }
135
+ part "tabs-content", "One role=tabpanel per tab - only the active panel is visible",
136
+ states: {
137
+ "data-hidden" => "panel is inactive (paired with the hidden property - the controller " \
138
+ "flips both)",
139
+ "data-value" => "the owning tab's value"
140
+ }
141
+
142
+ # @api private
143
+ def before_render
144
+ raise ArgumentError, "Tabs requires at least one with_tab" unless tabs?
145
+
146
+ return unless default.present? && tab_defs.none? { |tab| tab.value == default }
147
+
148
+ raise ArgumentError, "Tabs default: #{default.inspect} matches no tab value " \
149
+ "(#{tab_defs.map(&:value).inspect})"
150
+ end
151
+
152
+ # @api private
153
+ def tab_defs
154
+ @tab_defs ||= []
155
+ end
156
+
157
+ # The server-rendered active tab: default: when given, else the
158
+ # first enabled tab.
159
+ # @api private
160
+ def active_value
161
+ @active_value ||= default.presence || tab_defs.reject(&:disabled).first&.value || tab_defs.first.value
162
+ end
163
+
164
+ # @api private
165
+ def active?(tab)
166
+ tab.value == active_value
167
+ end
168
+
169
+ # @api private
170
+ def trigger_id(tab) = "#{instance_id}-trigger-#{tab.value}"
171
+ # @api private
172
+ def panel_id(tab) = "#{instance_id}-panel-#{tab.value}"
173
+
174
+ # defer: swaps the panel body for a lazy turbo-frame - a
175
+ # hidden panel is not visible, so Turbo fetches on first
176
+ # activation with zero tabs-controller involvement. The panel
177
+ # block (if given) becomes the frame's placeholder.
178
+ # @api private
179
+ def panel_body(tab)
180
+ return capture(&tab.panel) unless tab.defer
181
+
182
+ render(Poetry::Ui::Deferred::Component.new(src: tab.defer)) { tab.panel ? capture(&tab.panel) : nil }
183
+ end
184
+
185
+ # @api private
186
+ def root_attributes
187
+ html_attributes.merge_if_not_set(
188
+ {
189
+ "data-slot" => "tabs", "data-orientation" => orientation
190
+ }.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
191
+ )
192
+ end
193
+
194
+ # @api private
195
+ def list_attributes
196
+ attrs = {
197
+ "role" => "tablist", "data-slot" => "tabs-list", "data-variant" => variant,
198
+ "aria-orientation" => orientation,
199
+ "class" => "#{css(:list)} #{css(:"list_#{variant}")}".strip
200
+ }
201
+ attrs["aria-label"] = label if label.present?
202
+ attrs.merge(stimulus_attributes_for(:list))
203
+ end
204
+
205
+ # @api private
206
+ def trigger_attributes(tab)
207
+ attrs = {
208
+ "type" => "button", "role" => "tab", "id" => trigger_id(tab),
209
+ "data-slot" => "tabs-trigger", "data-value" => tab.value,
210
+ "data-poetry-collection-item" => "",
211
+ "aria-selected" => active?(tab).to_s,
212
+ "tabindex" => active?(tab) ? "0" : "-1",
213
+ "class" => css(:trigger)
214
+ }.merge(stimulus_attributes_for(:trigger))
215
+ attrs["aria-controls"] = panel_id(tab) unless tab.panel_less
216
+ attrs["data-active"] = "" if active?(tab)
217
+ if tab.disabled
218
+ attrs["disabled"] = true
219
+ attrs["data-disabled"] = "" # the roving-focus collection filter
220
+ end
221
+ attrs
222
+ end
223
+
224
+ # @api private
225
+ def panel_attributes(tab)
226
+ attrs = {
227
+ "role" => "tabpanel", "id" => panel_id(tab), "data-slot" => "tabs-content",
228
+ "data-value" => tab.value, "aria-labelledby" => trigger_id(tab),
229
+ "tabindex" => "0", "class" => css(:content)
230
+ }
231
+ unless active?(tab)
232
+ attrs["hidden"] = true
233
+ attrs["data-hidden"] = ""
234
+ end
235
+ attrs
236
+ end
237
+
238
+ # One declared tab (built by with_tab).
239
+ # @api private
240
+ Tab = Data.define(:title, :value, :disabled, :panel, :defer, :panel_less)
241
+
242
+ private
243
+
244
+ def instance_id
245
+ @instance_id ||= poetry_instance_id("poetry-tabs")
246
+ end
247
+
248
+ private :tab_defs, :active_value, :active?, :trigger_id, :panel_id, :panel_body, :root_attributes
249
+ private :list_attributes, :trigger_attributes, :panel_attributes
250
+ end
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Tabs
6
+ # The Tabs preview: the default boxed list, the line variant, and a
7
+ # vertical set with a disabled tab.
8
+ class Preview < Poetry::Core::Preview::Base
9
+ def default
10
+ render_component(label: "Account settings") do |tabs|
11
+ tabs.with_tab("Account", value: "account") { "Make changes to your account here." }
12
+ tabs.with_tab("Password", value: "password") { "Change your password here." }
13
+ end
14
+ end
15
+
16
+ def line_variant
17
+ render_component(variant: :line, label: "Docs sections", default: "usage") do |tabs|
18
+ tabs.with_tab("Overview", value: "overview") { "The component at a glance." }
19
+ tabs.with_tab("Usage", value: "usage") { "How to compose it." }
20
+ tabs.with_tab("API", value: "api") { "Options and slots." }
21
+ end
22
+ end
23
+
24
+ # The list-only arrangement (panel: false): no tabpanels at all,
25
+ # triggers without aria-controls - upstream's line/disabled demos.
26
+ def list_only
27
+ render_component(label: "List-only demo") do |tabs|
28
+ tabs.with_tab("Home", value: "home", panel: false)
29
+ tabs.with_tab("Disabled", value: "settings", disabled: true, panel: false)
30
+ end
31
+ end
32
+
33
+ def vertical_with_disabled
34
+ render_component(orientation: :vertical, label: "Project areas") do |tabs|
35
+ tabs.with_tab("General", value: "general") { "Project defaults." }
36
+ tabs.with_tab("Members", value: "members") { "Who has access." }
37
+ tabs.with_tab("Billing", value: "billing", disabled: true) { "Plan and invoices." }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Tabs
6
+ # Re-expressed through the cn-* theme layer. The group/tabs and
7
+ # group/tabs-list markers stay inline (the theme rules key on them);
8
+ # the trigger's whole text-color cluster moved theme-side TOGETHER
9
+ # (split-side rule: the base dims must lose to data-active in-layer).
10
+ # The after:* line-indicator geometry stays inline except its paint.
11
+ # The indicator's OPACITY PAIR (rest 0 / active 100) lives fully
12
+ # inline: the rest state is a utility, so a theme-side activation
13
+ # sits in layer(base) and loses to it unconditionally - splitting
14
+ # the pair leaves the line variant's indicator invisible.
15
+ class Style < Poetry::Core::Style
16
+ base "cn-tabs group/tabs flex data-horizontal:flex-col"
17
+
18
+ element :list, "cn-tabs-list group/tabs-list inline-flex w-fit items-center justify-center " \
19
+ "group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col"
20
+ element :list_default, "cn-tabs-list-variant-default"
21
+ element :list_line, "cn-tabs-list-variant-line"
22
+
23
+ element :trigger,
24
+ "cn-tabs-trigger relative inline-flex h-[calc(100%-1px)] flex-1 items-center " \
25
+ "justify-center whitespace-nowrap transition-all group-data-vertical/tabs:w-full " \
26
+ "group-data-vertical/tabs:justify-start focus-visible:border-ring " \
27
+ "focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 " \
28
+ "focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 " \
29
+ "aria-disabled:pointer-events-none aria-disabled:opacity-50 " \
30
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 after:absolute after:opacity-0 " \
31
+ "after:transition-opacity " \
32
+ "group-data-[variant=line]/tabs-list:data-active:after:opacity-100 " \
33
+ "after:bg-foreground dark:data-active:bg-input/30 dark:data-active:border-input " \
34
+ "dark:data-active:text-foreground " \
35
+ "dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent " \
36
+ "dark:group-data-[variant=line]/tabs-list:data-active:border-transparent " \
37
+ "dark:hover:text-foreground dark:text-muted-foreground data-active:bg-background " \
38
+ "data-active:text-foreground group-data-[variant=line]/tabs-list:bg-transparent " \
39
+ "group-data-[variant=line]/tabs-list:data-active:bg-transparent " \
40
+ "group-data-horizontal/tabs:after:bottom-[-5px] " \
41
+ "group-data-horizontal/tabs:after:h-0.5 group-data-horizontal/tabs:after:inset-x-0 " \
42
+ "group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:inset-y-0 " \
43
+ "group-data-vertical/tabs:after:w-0.5 hover:text-foreground text-foreground/60"
44
+
45
+ element :content, "cn-tabs-content flex-1 outline-none"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,8 @@
1
+ <%# Grid semantics: caption span (aria-labelledby - a grid is not a
2
+ labelable control), then the roving row collection. %>
3
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
4
+ <%= content_tag(:span, label, "id" => label_id, "data-slot" => "tag-group-label", "class" => css(:label)) %>
5
+ <%= content_tag(:div, **Poetry::Core::HTML::Attributes.new(grid_attributes).to_attributes) do %>
6
+ <% tags.each do |tag_slot| %><%= tag_slot %><% end %>
7
+ <% end %>
8
+ <% end %>