shadcn_view_component 0.3.3

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 (507) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +255 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +604 -0
  5. data/app/assets/stylesheets/shadcn-themes.css +1075 -0
  6. data/app/assets/stylesheets/shadcn.css +853 -0
  7. data/app/components/shadcn/accordion/component.rb +44 -0
  8. data/app/components/shadcn/accordion/content/component.rb +44 -0
  9. data/app/components/shadcn/accordion/item/component.rb +41 -0
  10. data/app/components/shadcn/accordion/preview.rb +16 -0
  11. data/app/components/shadcn/accordion/previews/default.html.erb +10 -0
  12. data/app/components/shadcn/accordion/previews/multiple.html.erb +13 -0
  13. data/app/components/shadcn/accordion/trigger/component.rb +52 -0
  14. data/app/components/shadcn/alert/component.rb +52 -0
  15. data/app/components/shadcn/alert/preview.rb +11 -0
  16. data/app/components/shadcn/alert/previews/default.html.erb +8 -0
  17. data/app/components/shadcn/alert.rb +17 -0
  18. data/app/components/shadcn/alert_dialog/action/component.rb +35 -0
  19. data/app/components/shadcn/alert_dialog/cancel/component.rb +16 -0
  20. data/app/components/shadcn/alert_dialog/component.rb +20 -0
  21. data/app/components/shadcn/alert_dialog/content/component.rb +55 -0
  22. data/app/components/shadcn/alert_dialog/header/component.rb +33 -0
  23. data/app/components/shadcn/alert_dialog/preview.rb +16 -0
  24. data/app/components/shadcn/alert_dialog/previews/default.html.erb +13 -0
  25. data/app/components/shadcn/alert_dialog/previews/small_with_media.html.erb +38 -0
  26. data/app/components/shadcn/alert_dialog.rb +32 -0
  27. data/app/components/shadcn/application_view_component.rb +225 -0
  28. data/app/components/shadcn/application_view_component_preview.rb +8 -0
  29. data/app/components/shadcn/aspect_ratio/component.rb +34 -0
  30. data/app/components/shadcn/aspect_ratio/preview.rb +16 -0
  31. data/app/components/shadcn/aspect_ratio/previews/default.html.erb +5 -0
  32. data/app/components/shadcn/aspect_ratio/previews/ratios.html.erb +12 -0
  33. data/app/components/shadcn/attachment/action/component.rb +19 -0
  34. data/app/components/shadcn/attachment/component.rb +76 -0
  35. data/app/components/shadcn/attachment/media/component.rb +56 -0
  36. data/app/components/shadcn/attachment/preview.rb +11 -0
  37. data/app/components/shadcn/attachment/previews/default.html.erb +80 -0
  38. data/app/components/shadcn/attachment/trigger/component.rb +31 -0
  39. data/app/components/shadcn/attachment.rb +42 -0
  40. data/app/components/shadcn/avatar/component.rb +43 -0
  41. data/app/components/shadcn/avatar/fallback/component.rb +23 -0
  42. data/app/components/shadcn/avatar/image/component.rb +20 -0
  43. data/app/components/shadcn/avatar/preview.rb +11 -0
  44. data/app/components/shadcn/avatar/previews/default.html.erb +13 -0
  45. data/app/components/shadcn/avatar.rb +32 -0
  46. data/app/components/shadcn/badge/component.rb +57 -0
  47. data/app/components/shadcn/badge/preview.rb +11 -0
  48. data/app/components/shadcn/badge/previews/default.html.erb +5 -0
  49. data/app/components/shadcn/breadcrumb/component.rb +15 -0
  50. data/app/components/shadcn/breadcrumb/ellipsis/component.rb +28 -0
  51. data/app/components/shadcn/breadcrumb/page/component.rb +25 -0
  52. data/app/components/shadcn/breadcrumb/preview.rb +16 -0
  53. data/app/components/shadcn/breadcrumb/previews/custom_separator.html.erb +17 -0
  54. data/app/components/shadcn/breadcrumb/previews/default.html.erb +15 -0
  55. data/app/components/shadcn/breadcrumb/separator/component.rb +31 -0
  56. data/app/components/shadcn/breadcrumb.rb +18 -0
  57. data/app/components/shadcn/bubble/component.rb +90 -0
  58. data/app/components/shadcn/bubble/preview.rb +16 -0
  59. data/app/components/shadcn/bubble/previews/alignment.html.erb +33 -0
  60. data/app/components/shadcn/bubble/previews/default.html.erb +22 -0
  61. data/app/components/shadcn/bubble/reactions/component.rb +54 -0
  62. data/app/components/shadcn/bubble.rb +24 -0
  63. data/app/components/shadcn/button/component.rb +85 -0
  64. data/app/components/shadcn/button/preview.rb +31 -0
  65. data/app/components/shadcn/button/previews/default.html.erb +8 -0
  66. data/app/components/shadcn/button/previews/sizes.html.erb +5 -0
  67. data/app/components/shadcn/button/previews/variants.html.erb +5 -0
  68. data/app/components/shadcn/button_group/component.rb +53 -0
  69. data/app/components/shadcn/button_group/preview.rb +15 -0
  70. data/app/components/shadcn/button_group/previews/default.html.erb +18 -0
  71. data/app/components/shadcn/button_group/previews/vertical.html.erb +5 -0
  72. data/app/components/shadcn/button_group/separator/component.rb +30 -0
  73. data/app/components/shadcn/button_group/text/component.rb +20 -0
  74. data/app/components/shadcn/calendar/component.rb +558 -0
  75. data/app/components/shadcn/calendar/month.rb +74 -0
  76. data/app/components/shadcn/calendar/preview.rb +64 -0
  77. data/app/components/shadcn/calendar/previews/booked.html.erb +18 -0
  78. data/app/components/shadcn/calendar/previews/custom_cell_size.html.erb +11 -0
  79. data/app/components/shadcn/calendar/previews/date_and_time.html.erb +31 -0
  80. data/app/components/shadcn/calendar/previews/default.html.erb +8 -0
  81. data/app/components/shadcn/calendar/previews/dropdown_caption.html.erb +11 -0
  82. data/app/components/shadcn/calendar/previews/in_a_form.html.erb +8 -0
  83. data/app/components/shadcn/calendar/previews/presets.html.erb +20 -0
  84. data/app/components/shadcn/calendar/previews/range.html.erb +10 -0
  85. data/app/components/shadcn/calendar/previews/week_numbers.html.erb +8 -0
  86. data/app/components/shadcn/card/component.rb +24 -0
  87. data/app/components/shadcn/card/header/component.rb +26 -0
  88. data/app/components/shadcn/card/preview.rb +16 -0
  89. data/app/components/shadcn/card/previews/default.html.erb +18 -0
  90. data/app/components/shadcn/card/previews/image.html.erb +37 -0
  91. data/app/components/shadcn/card.rb +21 -0
  92. data/app/components/shadcn/carousel/component.rb +61 -0
  93. data/app/components/shadcn/carousel/content/component.rb +86 -0
  94. data/app/components/shadcn/carousel/item/component.rb +42 -0
  95. data/app/components/shadcn/carousel/next/component.rb +59 -0
  96. data/app/components/shadcn/carousel/preview.rb +22 -0
  97. data/app/components/shadcn/carousel/previews/default.html.erb +22 -0
  98. data/app/components/shadcn/carousel/previews/sizes.html.erb +42 -0
  99. data/app/components/shadcn/carousel/previews/vertical.html.erb +27 -0
  100. data/app/components/shadcn/carousel/previous/component.rb +59 -0
  101. data/app/components/shadcn/chart/area/component.rb +43 -0
  102. data/app/components/shadcn/chart/bar/component.rb +75 -0
  103. data/app/components/shadcn/chart/cartesian/component.rb +152 -0
  104. data/app/components/shadcn/chart/component.rb +124 -0
  105. data/app/components/shadcn/chart/focusable.rb +38 -0
  106. data/app/components/shadcn/chart/legend/component.rb +55 -0
  107. data/app/components/shadcn/chart/line/component.rb +50 -0
  108. data/app/components/shadcn/chart/pie/component.rb +169 -0
  109. data/app/components/shadcn/chart/plot.rb +147 -0
  110. data/app/components/shadcn/chart/preview.rb +45 -0
  111. data/app/components/shadcn/chart/previews/area.html.erb +31 -0
  112. data/app/components/shadcn/chart/previews/bar.html.erb +32 -0
  113. data/app/components/shadcn/chart/previews/bar_stacked.html.erb +30 -0
  114. data/app/components/shadcn/chart/previews/default.html.erb +30 -0
  115. data/app/components/shadcn/chart/previews/donut.html.erb +16 -0
  116. data/app/components/shadcn/chart/previews/line.html.erb +30 -0
  117. data/app/components/shadcn/chart/previews/percentages.html.erb +17 -0
  118. data/app/components/shadcn/chart/table/component.rb +75 -0
  119. data/app/components/shadcn/chart/tooltip/component.rb +111 -0
  120. data/app/components/shadcn/checkbox/component.rb +109 -0
  121. data/app/components/shadcn/checkbox/preview.rb +16 -0
  122. data/app/components/shadcn/checkbox/previews/default.html.erb +14 -0
  123. data/app/components/shadcn/checkbox/previews/states.html.erb +23 -0
  124. data/app/components/shadcn/collapsible/component.rb +35 -0
  125. data/app/components/shadcn/collapsible/content/component.rb +20 -0
  126. data/app/components/shadcn/collapsible/preview.rb +11 -0
  127. data/app/components/shadcn/collapsible/previews/default.html.erb +9 -0
  128. data/app/components/shadcn/collapsible/trigger/component.rb +23 -0
  129. data/app/components/shadcn/combobox/chip/component.rb +60 -0
  130. data/app/components/shadcn/combobox/chip_input/component.rb +43 -0
  131. data/app/components/shadcn/combobox/chips/component.rb +54 -0
  132. data/app/components/shadcn/combobox/clear/component.rb +32 -0
  133. data/app/components/shadcn/combobox/component.rb +113 -0
  134. data/app/components/shadcn/combobox/content/component.rb +59 -0
  135. data/app/components/shadcn/combobox/empty/component.rb +23 -0
  136. data/app/components/shadcn/combobox/input/component.rb +85 -0
  137. data/app/components/shadcn/combobox/item/component.rb +69 -0
  138. data/app/components/shadcn/combobox/list/component.rb +27 -0
  139. data/app/components/shadcn/combobox/preview.rb +31 -0
  140. data/app/components/shadcn/combobox/previews/default.html.erb +20 -0
  141. data/app/components/shadcn/combobox/previews/grouped.html.erb +25 -0
  142. data/app/components/shadcn/combobox/previews/multiple.html.erb +28 -0
  143. data/app/components/shadcn/combobox/previews/with_clear.html.erb +16 -0
  144. data/app/components/shadcn/combobox/trigger/component.rb +54 -0
  145. data/app/components/shadcn/combobox.rb +16 -0
  146. data/app/components/shadcn/command/component.rb +67 -0
  147. data/app/components/shadcn/command/dialog/component.rb +72 -0
  148. data/app/components/shadcn/command/empty/component.rb +29 -0
  149. data/app/components/shadcn/command/group/component.rb +63 -0
  150. data/app/components/shadcn/command/input/component.rb +66 -0
  151. data/app/components/shadcn/command/item/component.rb +62 -0
  152. data/app/components/shadcn/command/list/component.rb +36 -0
  153. data/app/components/shadcn/command/preview.rb +26 -0
  154. data/app/components/shadcn/command/previews/default.html.erb +41 -0
  155. data/app/components/shadcn/command/previews/dialog.html.erb +33 -0
  156. data/app/components/shadcn/command/previews/ranking.html.erb +16 -0
  157. data/app/components/shadcn/command/separator/component.rb +33 -0
  158. data/app/components/shadcn/command.rb +14 -0
  159. data/app/components/shadcn/concerns/modal_root.rb +43 -0
  160. data/app/components/shadcn/concerns/submits_value.rb +81 -0
  161. data/app/components/shadcn/context_menu/component.rb +45 -0
  162. data/app/components/shadcn/context_menu/content/component.rb +40 -0
  163. data/app/components/shadcn/context_menu/preview.rb +16 -0
  164. data/app/components/shadcn/context_menu/previews/default.html.erb +34 -0
  165. data/app/components/shadcn/context_menu/previews/items.html.erb +35 -0
  166. data/app/components/shadcn/context_menu/sub/component.rb +47 -0
  167. data/app/components/shadcn/context_menu/sub_content/component.rb +31 -0
  168. data/app/components/shadcn/context_menu/trigger/component.rb +50 -0
  169. data/app/components/shadcn/context_menu.rb +43 -0
  170. data/app/components/shadcn/dialog/close/component.rb +20 -0
  171. data/app/components/shadcn/dialog/component.rb +20 -0
  172. data/app/components/shadcn/dialog/content/component.rb +78 -0
  173. data/app/components/shadcn/dialog/header/component.rb +23 -0
  174. data/app/components/shadcn/dialog/overlay/component.rb +28 -0
  175. data/app/components/shadcn/dialog/preview.rb +28 -0
  176. data/app/components/shadcn/dialog/previews/default.html.erb +17 -0
  177. data/app/components/shadcn/dialog/previews/inside_stacking_context.html.erb +25 -0
  178. data/app/components/shadcn/dialog/previews/no_close_button.html.erb +15 -0
  179. data/app/components/shadcn/dialog/previews/scrollable_content.html.erb +29 -0
  180. data/app/components/shadcn/dialog/trigger/component.rb +24 -0
  181. data/app/components/shadcn/dialog.rb +17 -0
  182. data/app/components/shadcn/drawer/component.rb +33 -0
  183. data/app/components/shadcn/drawer/content/component.rb +95 -0
  184. data/app/components/shadcn/drawer/header/component.rb +34 -0
  185. data/app/components/shadcn/drawer/preview.rb +29 -0
  186. data/app/components/shadcn/drawer/previews/default.html.erb +21 -0
  187. data/app/components/shadcn/drawer/previews/scrollable.html.erb +20 -0
  188. data/app/components/shadcn/drawer/previews/scrollable_side.html.erb +21 -0
  189. data/app/components/shadcn/drawer/previews/sides.html.erb +17 -0
  190. data/app/components/shadcn/drawer.rb +27 -0
  191. data/app/components/shadcn/dropdown_menu/checkbox_item/component.rb +61 -0
  192. data/app/components/shadcn/dropdown_menu/component.rb +48 -0
  193. data/app/components/shadcn/dropdown_menu/content/component.rb +49 -0
  194. data/app/components/shadcn/dropdown_menu/group/component.rb +16 -0
  195. data/app/components/shadcn/dropdown_menu/item/component.rb +50 -0
  196. data/app/components/shadcn/dropdown_menu/preview.rb +16 -0
  197. data/app/components/shadcn/dropdown_menu/previews/checkboxes_and_radio.html.erb +34 -0
  198. data/app/components/shadcn/dropdown_menu/previews/default.html.erb +23 -0
  199. data/app/components/shadcn/dropdown_menu/radio_group/component.rb +16 -0
  200. data/app/components/shadcn/dropdown_menu/radio_item/component.rb +63 -0
  201. data/app/components/shadcn/dropdown_menu/sub/component.rb +44 -0
  202. data/app/components/shadcn/dropdown_menu/sub_content/component.rb +45 -0
  203. data/app/components/shadcn/dropdown_menu/sub_trigger/component.rb +58 -0
  204. data/app/components/shadcn/dropdown_menu/trigger/component.rb +25 -0
  205. data/app/components/shadcn/dropdown_menu.rb +18 -0
  206. data/app/components/shadcn/empty/component.rb +17 -0
  207. data/app/components/shadcn/empty/media/component.rb +48 -0
  208. data/app/components/shadcn/empty/preview.rb +16 -0
  209. data/app/components/shadcn/empty/previews/default.html.erb +15 -0
  210. data/app/components/shadcn/empty/previews/media.html.erb +32 -0
  211. data/app/components/shadcn/empty.rb +26 -0
  212. data/app/components/shadcn/field/component.rb +61 -0
  213. data/app/components/shadcn/field/error/component.rb +49 -0
  214. data/app/components/shadcn/field/legend/component.rb +30 -0
  215. data/app/components/shadcn/field/preview.rb +17 -0
  216. data/app/components/shadcn/field/previews/default.html.erb +29 -0
  217. data/app/components/shadcn/field/previews/form_builder.html.erb +38 -0
  218. data/app/components/shadcn/field/separator/component.rb +35 -0
  219. data/app/components/shadcn/field.rb +44 -0
  220. data/app/components/shadcn/hover_card/component.rb +51 -0
  221. data/app/components/shadcn/hover_card/content/component.rb +45 -0
  222. data/app/components/shadcn/hover_card/preview.rb +16 -0
  223. data/app/components/shadcn/hover_card/previews/default.html.erb +36 -0
  224. data/app/components/shadcn/hover_card/previews/sides.html.erb +15 -0
  225. data/app/components/shadcn/hover_card/trigger/component.rb +33 -0
  226. data/app/components/shadcn/icon/component.rb +76 -0
  227. data/app/components/shadcn/icon.rb +43 -0
  228. data/app/components/shadcn/input/component.rb +26 -0
  229. data/app/components/shadcn/input/preview.rb +16 -0
  230. data/app/components/shadcn/input/previews/default.html.erb +5 -0
  231. data/app/components/shadcn/input/previews/states.html.erb +20 -0
  232. data/app/components/shadcn/input_group/addon/component.rb +65 -0
  233. data/app/components/shadcn/input_group/button/component.rb +40 -0
  234. data/app/components/shadcn/input_group/component.rb +43 -0
  235. data/app/components/shadcn/input_group/input/component.rb +25 -0
  236. data/app/components/shadcn/input_group/preview.rb +17 -0
  237. data/app/components/shadcn/input_group/previews/addons.html.erb +53 -0
  238. data/app/components/shadcn/input_group/previews/default.html.erb +25 -0
  239. data/app/components/shadcn/input_group/text/component.rb +20 -0
  240. data/app/components/shadcn/input_group/textarea/component.rb +21 -0
  241. data/app/components/shadcn/input_otp/component.rb +95 -0
  242. data/app/components/shadcn/input_otp/preview.rb +17 -0
  243. data/app/components/shadcn/input_otp/previews/default.html.erb +19 -0
  244. data/app/components/shadcn/input_otp/previews/variations.html.erb +33 -0
  245. data/app/components/shadcn/input_otp/separator/component.rb +21 -0
  246. data/app/components/shadcn/input_otp/slot/component.rb +60 -0
  247. data/app/components/shadcn/input_otp.rb +11 -0
  248. data/app/components/shadcn/item/component.rb +56 -0
  249. data/app/components/shadcn/item/group/component.rb +53 -0
  250. data/app/components/shadcn/item/media/component.rb +53 -0
  251. data/app/components/shadcn/item/preview.rb +20 -0
  252. data/app/components/shadcn/item/previews/default.html.erb +32 -0
  253. data/app/components/shadcn/item/previews/link.html.erb +39 -0
  254. data/app/components/shadcn/item/previews/outline.html.erb +8 -0
  255. data/app/components/shadcn/item/separator/component.rb +24 -0
  256. data/app/components/shadcn/item.rb +34 -0
  257. data/app/components/shadcn/kbd/component.rb +23 -0
  258. data/app/components/shadcn/kbd/group/component.rb +17 -0
  259. data/app/components/shadcn/kbd/preview.rb +11 -0
  260. data/app/components/shadcn/kbd/previews/default.html.erb +4 -0
  261. data/app/components/shadcn/label/component.rb +20 -0
  262. data/app/components/shadcn/label/preview.rb +11 -0
  263. data/app/components/shadcn/label/previews/default.html.erb +4 -0
  264. data/app/components/shadcn/marker/component.rb +46 -0
  265. data/app/components/shadcn/marker/icon/component.rb +23 -0
  266. data/app/components/shadcn/marker/preview.rb +17 -0
  267. data/app/components/shadcn/marker/previews/default.html.erb +18 -0
  268. data/app/components/shadcn/marker/previews/status.html.erb +32 -0
  269. data/app/components/shadcn/marker.rb +17 -0
  270. data/app/components/shadcn/menubar/component.rb +36 -0
  271. data/app/components/shadcn/menubar/content/component.rb +49 -0
  272. data/app/components/shadcn/menubar/menu/component.rb +39 -0
  273. data/app/components/shadcn/menubar/preview.rb +16 -0
  274. data/app/components/shadcn/menubar/previews/default.html.erb +52 -0
  275. data/app/components/shadcn/menubar/previews/items.html.erb +38 -0
  276. data/app/components/shadcn/menubar/sub/component.rb +43 -0
  277. data/app/components/shadcn/menubar/sub_content/component.rb +31 -0
  278. data/app/components/shadcn/menubar/sub_trigger/component.rb +31 -0
  279. data/app/components/shadcn/menubar/trigger/component.rb +41 -0
  280. data/app/components/shadcn/menubar.rb +47 -0
  281. data/app/components/shadcn/message/component.rb +33 -0
  282. data/app/components/shadcn/message/preview.rb +16 -0
  283. data/app/components/shadcn/message/previews/actions.html.erb +51 -0
  284. data/app/components/shadcn/message/previews/default.html.erb +35 -0
  285. data/app/components/shadcn/message.rb +35 -0
  286. data/app/components/shadcn/message_scroller/button/component.rb +78 -0
  287. data/app/components/shadcn/message_scroller/component.rb +59 -0
  288. data/app/components/shadcn/message_scroller/content/component.rb +50 -0
  289. data/app/components/shadcn/message_scroller/item/component.rb +45 -0
  290. data/app/components/shadcn/message_scroller/preview.rb +11 -0
  291. data/app/components/shadcn/message_scroller/previews/default.html.erb +72 -0
  292. data/app/components/shadcn/message_scroller/viewport/component.rb +41 -0
  293. data/app/components/shadcn/mode_switcher/component.rb +67 -0
  294. data/app/components/shadcn/mode_switcher/preview.rb +11 -0
  295. data/app/components/shadcn/mode_switcher/previews/default.html.erb +5 -0
  296. data/app/components/shadcn/mode_toggle/component.rb +75 -0
  297. data/app/components/shadcn/mode_toggle/preview.rb +11 -0
  298. data/app/components/shadcn/mode_toggle/previews/default.html.erb +20 -0
  299. data/app/components/shadcn/native_select/component.rb +59 -0
  300. data/app/components/shadcn/native_select/preview.rb +16 -0
  301. data/app/components/shadcn/native_select/previews/default.html.erb +9 -0
  302. data/app/components/shadcn/native_select/previews/groups.html.erb +26 -0
  303. data/app/components/shadcn/native_select.rb +16 -0
  304. data/app/components/shadcn/navigation_menu/component.rb +51 -0
  305. data/app/components/shadcn/navigation_menu/content/component.rb +58 -0
  306. data/app/components/shadcn/navigation_menu/preview.rb +11 -0
  307. data/app/components/shadcn/navigation_menu/previews/default.html.erb +49 -0
  308. data/app/components/shadcn/navigation_menu/trigger/component.rb +59 -0
  309. data/app/components/shadcn/navigation_menu.rb +28 -0
  310. data/app/components/shadcn/pagination/component.rb +19 -0
  311. data/app/components/shadcn/pagination/ellipsis/component.rb +28 -0
  312. data/app/components/shadcn/pagination/link/component.rb +37 -0
  313. data/app/components/shadcn/pagination/next/component.rb +31 -0
  314. data/app/components/shadcn/pagination/preview.rb +16 -0
  315. data/app/components/shadcn/pagination/previews/default.html.erb +18 -0
  316. data/app/components/shadcn/pagination/previews/simple.html.erb +39 -0
  317. data/app/components/shadcn/pagination/previous/component.rb +31 -0
  318. data/app/components/shadcn/pagination.rb +14 -0
  319. data/app/components/shadcn/parts.rb +38 -0
  320. data/app/components/shadcn/popover/anchor/component.rb +17 -0
  321. data/app/components/shadcn/popover/component.rb +42 -0
  322. data/app/components/shadcn/popover/content/component.rb +40 -0
  323. data/app/components/shadcn/popover/preview.rb +28 -0
  324. data/app/components/shadcn/popover/previews/align.html.erb +13 -0
  325. data/app/components/shadcn/popover/previews/default.html.erb +9 -0
  326. data/app/components/shadcn/popover/previews/inside_containing_block.html.erb +41 -0
  327. data/app/components/shadcn/popover/previews/inside_stacking_context.html.erb +30 -0
  328. data/app/components/shadcn/popover/trigger/component.rb +24 -0
  329. data/app/components/shadcn/popover.rb +16 -0
  330. data/app/components/shadcn/progress/component.rb +62 -0
  331. data/app/components/shadcn/progress/preview.rb +11 -0
  332. data/app/components/shadcn/progress/previews/default.html.erb +5 -0
  333. data/app/components/shadcn/radio_group/component.rb +51 -0
  334. data/app/components/shadcn/radio_group/item/component.rb +74 -0
  335. data/app/components/shadcn/radio_group/preview.rb +16 -0
  336. data/app/components/shadcn/radio_group/previews/default.html.erb +15 -0
  337. data/app/components/shadcn/radio_group/previews/states.html.erb +41 -0
  338. data/app/components/shadcn/resizable/component.rb +59 -0
  339. data/app/components/shadcn/resizable/handle/component.rb +70 -0
  340. data/app/components/shadcn/resizable/panel/component.rb +53 -0
  341. data/app/components/shadcn/resizable/preview.rb +23 -0
  342. data/app/components/shadcn/resizable/previews/default.html.erb +25 -0
  343. data/app/components/shadcn/resizable/previews/vertical.html.erb +19 -0
  344. data/app/components/shadcn/resizable/previews/with_handle.html.erb +17 -0
  345. data/app/components/shadcn/scroll_area/component.rb +84 -0
  346. data/app/components/shadcn/scroll_area/preview.rb +11 -0
  347. data/app/components/shadcn/scroll_area/previews/default.html.erb +36 -0
  348. data/app/components/shadcn/scroll_area/scrollbar/component.rb +72 -0
  349. data/app/components/shadcn/scroll_area/viewport/component.rb +70 -0
  350. data/app/components/shadcn/scroll_area.rb +14 -0
  351. data/app/components/shadcn/select/component.rb +67 -0
  352. data/app/components/shadcn/select/content/component.rb +157 -0
  353. data/app/components/shadcn/select/empty/component.rb +38 -0
  354. data/app/components/shadcn/select/group/component.rb +16 -0
  355. data/app/components/shadcn/select/item/component.rb +71 -0
  356. data/app/components/shadcn/select/list/component.rb +41 -0
  357. data/app/components/shadcn/select/preview.rb +24 -0
  358. data/app/components/shadcn/select/previews/default.html.erb +17 -0
  359. data/app/components/shadcn/select/previews/scrollable.html.erb +19 -0
  360. data/app/components/shadcn/select/previews/searchable.html.erb +18 -0
  361. data/app/components/shadcn/select/previews/states.html.erb +25 -0
  362. data/app/components/shadcn/select/search/component.rb +54 -0
  363. data/app/components/shadcn/select/trigger/component.rb +78 -0
  364. data/app/components/shadcn/select/value/component.rb +28 -0
  365. data/app/components/shadcn/select.rb +15 -0
  366. data/app/components/shadcn/separator/component.rb +41 -0
  367. data/app/components/shadcn/separator/preview.rb +11 -0
  368. data/app/components/shadcn/separator/previews/default.html.erb +9 -0
  369. data/app/components/shadcn/sheet/component.rb +16 -0
  370. data/app/components/shadcn/sheet/content/component.rb +98 -0
  371. data/app/components/shadcn/sheet/header/component.rb +23 -0
  372. data/app/components/shadcn/sheet/preview.rb +16 -0
  373. data/app/components/shadcn/sheet/previews/default.html.erb +16 -0
  374. data/app/components/shadcn/sheet/previews/no_close_button.html.erb +14 -0
  375. data/app/components/shadcn/sheet.rb +22 -0
  376. data/app/components/shadcn/sidebar/component.rb +232 -0
  377. data/app/components/shadcn/sidebar/input/component.rb +25 -0
  378. data/app/components/shadcn/sidebar/menu_action/component.rb +58 -0
  379. data/app/components/shadcn/sidebar/menu_button/component.rb +100 -0
  380. data/app/components/shadcn/sidebar/menu_skeleton/component.rb +51 -0
  381. data/app/components/shadcn/sidebar/menu_sub_button/component.rb +60 -0
  382. data/app/components/shadcn/sidebar/preview.rb +22 -0
  383. data/app/components/shadcn/sidebar/previews/default.html.erb +189 -0
  384. data/app/components/shadcn/sidebar/previews/floating.html.erb +39 -0
  385. data/app/components/shadcn/sidebar/previews/inset.html.erb +48 -0
  386. data/app/components/shadcn/sidebar/provider/component.rb +53 -0
  387. data/app/components/shadcn/sidebar/rail/component.rb +43 -0
  388. data/app/components/shadcn/sidebar/separator/component.rb +23 -0
  389. data/app/components/shadcn/sidebar/trigger/component.rb +39 -0
  390. data/app/components/shadcn/sidebar.rb +98 -0
  391. data/app/components/shadcn/skeleton/component.rb +14 -0
  392. data/app/components/shadcn/skeleton/preview.rb +11 -0
  393. data/app/components/shadcn/skeleton/previews/default.html.erb +7 -0
  394. data/app/components/shadcn/slider/component.rb +145 -0
  395. data/app/components/shadcn/slider/preview.rb +16 -0
  396. data/app/components/shadcn/slider/previews/default.html.erb +17 -0
  397. data/app/components/shadcn/slider/previews/vertical.html.erb +7 -0
  398. data/app/components/shadcn/slider/range/component.rb +51 -0
  399. data/app/components/shadcn/slider/thumb/component.rb +58 -0
  400. data/app/components/shadcn/slider/track/component.rb +32 -0
  401. data/app/components/shadcn/spinner/component.rb +21 -0
  402. data/app/components/shadcn/spinner/preview.rb +11 -0
  403. data/app/components/shadcn/spinner/previews/default.html.erb +7 -0
  404. data/app/components/shadcn/switch/component.rb +104 -0
  405. data/app/components/shadcn/switch/preview.rb +11 -0
  406. data/app/components/shadcn/switch/previews/default.html.erb +24 -0
  407. data/app/components/shadcn/table/component.rb +26 -0
  408. data/app/components/shadcn/table/preview.rb +16 -0
  409. data/app/components/shadcn/table/previews/default.html.erb +19 -0
  410. data/app/components/shadcn/table/previews/footer.html.erb +25 -0
  411. data/app/components/shadcn/table.rb +33 -0
  412. data/app/components/shadcn/tabs/component.rb +40 -0
  413. data/app/components/shadcn/tabs/content/component.rb +34 -0
  414. data/app/components/shadcn/tabs/list/component.rb +51 -0
  415. data/app/components/shadcn/tabs/preview.rb +16 -0
  416. data/app/components/shadcn/tabs/previews/default.html.erb +21 -0
  417. data/app/components/shadcn/tabs/previews/line.html.erb +23 -0
  418. data/app/components/shadcn/tabs/trigger/component.rb +66 -0
  419. data/app/components/shadcn/textarea/component.rb +24 -0
  420. data/app/components/shadcn/textarea/preview.rb +16 -0
  421. data/app/components/shadcn/textarea/previews/default.html.erb +3 -0
  422. data/app/components/shadcn/textarea/previews/states.html.erb +18 -0
  423. data/app/components/shadcn/theme_selector/component.rb +88 -0
  424. data/app/components/shadcn/theme_selector/preview.rb +17 -0
  425. data/app/components/shadcn/theme_selector/previews/all_themes.html.erb +20 -0
  426. data/app/components/shadcn/theme_selector/previews/default.html.erb +18 -0
  427. data/app/components/shadcn/toaster/component.rb +139 -0
  428. data/app/components/shadcn/toaster/preview.rb +16 -0
  429. data/app/components/shadcn/toaster/previews/default.html.erb +19 -0
  430. data/app/components/shadcn/toaster/previews/from_the_server.html.erb +15 -0
  431. data/app/components/shadcn/toaster/toast/component.rb +134 -0
  432. data/app/components/shadcn/toggle/component.rb +81 -0
  433. data/app/components/shadcn/toggle/preview.rb +11 -0
  434. data/app/components/shadcn/toggle/previews/default.html.erb +6 -0
  435. data/app/components/shadcn/toggle_group/component.rb +62 -0
  436. data/app/components/shadcn/toggle_group/item/component.rb +59 -0
  437. data/app/components/shadcn/toggle_group/preview.rb +16 -0
  438. data/app/components/shadcn/toggle_group/previews/default.html.erb +12 -0
  439. data/app/components/shadcn/toggle_group/previews/sizes.html.erb +37 -0
  440. data/app/components/shadcn/tooltip/component.rb +38 -0
  441. data/app/components/shadcn/tooltip/content/component.rb +62 -0
  442. data/app/components/shadcn/tooltip/preview.rb +21 -0
  443. data/app/components/shadcn/tooltip/previews/default.html.erb +4 -0
  444. data/app/components/shadcn/tooltip/previews/sides.html.erb +19 -0
  445. data/app/components/shadcn/tooltip/previews/with_keyboard_shortcut.html.erb +11 -0
  446. data/app/components/shadcn/tooltip/provider/component.rb +28 -0
  447. data/app/components/shadcn/tooltip/trigger/component.rb +25 -0
  448. data/app/helpers/shadcn_view_component/form_helper.rb +25 -0
  449. data/app/helpers/shadcn_view_component/theme_helper.rb +61 -0
  450. data/app/javascript/shadcn/animation.js +180 -0
  451. data/app/javascript/shadcn/command_score.js +110 -0
  452. data/app/javascript/shadcn/controllers/accordion_controller.js +127 -0
  453. data/app/javascript/shadcn/controllers/avatar_controller.js +32 -0
  454. data/app/javascript/shadcn/controllers/calendar_controller.js +633 -0
  455. data/app/javascript/shadcn/controllers/carousel_controller.js +139 -0
  456. data/app/javascript/shadcn/controllers/chart_controller.js +110 -0
  457. data/app/javascript/shadcn/controllers/checkbox_controller.js +61 -0
  458. data/app/javascript/shadcn/controllers/collapsible_controller.js +55 -0
  459. data/app/javascript/shadcn/controllers/combobox_controller.js +360 -0
  460. data/app/javascript/shadcn/controllers/command_controller.js +199 -0
  461. data/app/javascript/shadcn/controllers/dialog_controller.js +154 -0
  462. data/app/javascript/shadcn/controllers/drawer_controller.js +265 -0
  463. data/app/javascript/shadcn/controllers/dropdown_menu_controller.js +259 -0
  464. data/app/javascript/shadcn/controllers/hover_card_controller.js +85 -0
  465. data/app/javascript/shadcn/controllers/input_group_controller.js +17 -0
  466. data/app/javascript/shadcn/controllers/input_otp_controller.js +80 -0
  467. data/app/javascript/shadcn/controllers/menubar_controller.js +151 -0
  468. data/app/javascript/shadcn/controllers/message_scroller_controller.js +456 -0
  469. data/app/javascript/shadcn/controllers/navigation_menu_controller.js +211 -0
  470. data/app/javascript/shadcn/controllers/popover_controller.js +66 -0
  471. data/app/javascript/shadcn/controllers/radio_group_controller.js +69 -0
  472. data/app/javascript/shadcn/controllers/resizable_controller.js +167 -0
  473. data/app/javascript/shadcn/controllers/scroll_area_controller.js +253 -0
  474. data/app/javascript/shadcn/controllers/select_controller.js +320 -0
  475. data/app/javascript/shadcn/controllers/sidebar_controller.js +273 -0
  476. data/app/javascript/shadcn/controllers/slider_controller.js +230 -0
  477. data/app/javascript/shadcn/controllers/switch_controller.js +39 -0
  478. data/app/javascript/shadcn/controllers/tabs_controller.js +81 -0
  479. data/app/javascript/shadcn/controllers/theme_controller.js +67 -0
  480. data/app/javascript/shadcn/controllers/toaster_controller.js +281 -0
  481. data/app/javascript/shadcn/controllers/toggle_controller.js +24 -0
  482. data/app/javascript/shadcn/controllers/toggle_group_controller.js +73 -0
  483. data/app/javascript/shadcn/controllers/tooltip_controller.js +58 -0
  484. data/app/javascript/shadcn/direction.js +41 -0
  485. data/app/javascript/shadcn/dismiss.js +73 -0
  486. data/app/javascript/shadcn/floating.js +207 -0
  487. data/app/javascript/shadcn/focus.js +86 -0
  488. data/app/javascript/shadcn/id.js +14 -0
  489. data/app/javascript/shadcn/index.js +110 -0
  490. data/app/javascript/shadcn/popper.js +262 -0
  491. data/app/javascript/shadcn/scroll_geometry.js +267 -0
  492. data/app/javascript/shadcn/theme.js +161 -0
  493. data/app/javascript/shadcn/top_layer.js +59 -0
  494. data/app/javascript/shadcn/typeahead.js +99 -0
  495. data/config/importmap.rb +6 -0
  496. data/config/locales/en.yml +62 -0
  497. data/lib/generators/shadcn_view_component/install/install_generator.rb +149 -0
  498. data/lib/shadcn_view_component/engine.rb +73 -0
  499. data/lib/shadcn_view_component/form_builder.rb +264 -0
  500. data/lib/shadcn_view_component/icon_registry.rb +66 -0
  501. data/lib/shadcn_view_component/icons.rb +32 -0
  502. data/lib/shadcn_view_component/themes.rb +1065 -0
  503. data/lib/shadcn_view_component/version.rb +3 -0
  504. data/lib/shadcn_view_component.rb +82 -0
  505. data/lib/tasks/icons.rake +53 -0
  506. data/lib/tasks/themes.rake +171 -0
  507. metadata +608 -0
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module ToggleGroup
5
+ module Item
6
+ # ToggleGroupItem — styled with the toggle's own variants, like the TSX
7
+ # does via `toggleVariants({ variant, size })`.
8
+ class Component < ApplicationViewComponent
9
+ EXTRA_CLASSES = "w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10 " \
10
+ "data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none " \
11
+ "data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md " \
12
+ "data-[spacing=0]:data-[variant=outline]:border-l-0 " \
13
+ "data-[spacing=0]:data-[variant=outline]:first:border-l"
14
+
15
+ default_tag :button
16
+ slot_name :"toggle-group-item"
17
+
18
+ attr_reader :value, :variant, :size, :spacing, :pressed, :disabled
19
+
20
+ def initialize(value:, variant: :default, size: :default, spacing: 0,
21
+ pressed: false, disabled: false, **attributes)
22
+ @value = value.to_s
23
+ @variant = variant&.to_sym || :default
24
+ @size = size&.to_sym || :default
25
+ @spacing = spacing
26
+ @pressed = pressed
27
+ @disabled = disabled
28
+ super(**attributes)
29
+ end
30
+
31
+ def element_attributes(**defaults)
32
+ super(**{
33
+ type: "button",
34
+ disabled: (true if disabled),
35
+ tabindex: "-1",
36
+ "aria-pressed" => pressed.to_s,
37
+ "data-state" => (pressed ? "on" : "off"),
38
+ "data-value" => value,
39
+ "data-variant" => variant,
40
+ "data-size" => size,
41
+ "data-spacing" => spacing,
42
+ "data-disabled" => (true if disabled),
43
+ "data-shadcn--toggle-group-target" => "item",
44
+ "data-action" => "click->shadcn--toggle-group#toggle " \
45
+ "keydown->shadcn--toggle-group#keydown"
46
+ }.merge(defaults))
47
+ end
48
+
49
+ def css_classes(extra = nil)
50
+ Toggle::Component.variant_classes(
51
+ variant:,
52
+ size:,
53
+ class: ShadcnViewComponent.cn(EXTRA_CLASSES, extra)
54
+ )
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module ToggleGroup
5
+ class Preview < ApplicationViewComponentPreview
6
+ def default
7
+ render_with_template
8
+ end
9
+
10
+ # Upstream's "Size" and "Disabled". Its "Vertical" is the other registry's.
11
+ def sizes
12
+ render_with_template
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ <div class="flex flex-col items-start gap-6">
2
+ <%= render(Shadcn::ToggleGroup::Component.new(type: :multiple, variant: :outline)) do |g| %>
3
+ <% %w[Bold Italic Underline].each do |label| %>
4
+ <% g.with_item(value: label.downcase) { label } %>
5
+ <% end %>
6
+ <% end %>
7
+ <%= render(Shadcn::ToggleGroup::Component.new(type: :single, spacing: 2, value: "center")) do |g| %>
8
+ <% %w[Left Center Right].each do |label| %>
9
+ <% g.with_item(value: label.downcase, pressed: label == "Center") { label } %>
10
+ <% end %>
11
+ <% end %>
12
+ </div>
@@ -0,0 +1,37 @@
1
+ <%# Upstream's "Size" and "Disabled".
2
+
3
+ Icon-only items, because that is where the size shows. The three are
4
+ `h-8 min-w-8`, `h-9 min-w-9` and `h-10 min-w-10` — with a text label the
5
+ width comes from the text and only 4px of height separates them, which is
6
+ what the first version of this preview looked like: three rows the same.
7
+
8
+ Upstream's "Vertical" is not here. `toggle-group.tsx` takes no orientation
9
+ at all, so there is nothing in this registry to show — the first version
10
+ passed `orientation:`, which landed as an inert HTML attribute, and the
11
+ version after it reached for `flex-col`, which stacks items of three
12
+ different widths because the group is `w-fit`. Both were this port
13
+ inventing a variant. See todo.md for the others upstream's docs show and
14
+ this registry does not have. %>
15
+ <div class="flex items-center gap-6">
16
+ <% %i[sm default lg].each do |size| %>
17
+ <div class="flex flex-col items-center gap-1">
18
+ <%= render(Shadcn::ToggleGroup::Component.new(type: :single, variant: :outline, size:, value: "check")) do |g| %>
19
+ <% %w[check circle x].each do |icon| %>
20
+ <% g.with_item(value: icon, pressed: icon == "check", "aria-label": icon) do %>
21
+ <%= render(Shadcn::Icon::Component.new(icon)) %>
22
+ <% end %>
23
+ <% end %>
24
+ <% end %>
25
+ <span class="text-xs text-muted-foreground"><%= size %></span>
26
+ </div>
27
+ <% end %>
28
+
29
+ <div class="flex flex-col items-center gap-1">
30
+ <%= render(Shadcn::ToggleGroup::Component.new(type: :single, variant: :outline, disabled: true, value: "b")) do |g| %>
31
+ <% %w[A B C].each do |label| %>
32
+ <% g.with_item(value: label.downcase, pressed: label == "B") { label } %>
33
+ <% end %>
34
+ <% end %>
35
+ <span class="text-xs text-muted-foreground">disabled</span>
36
+ </div>
37
+ </div>
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Tooltip
5
+ # Port of registry/new-york-v4/ui/tooltip.tsx
6
+ class Component < ApplicationViewComponent
7
+ renders_one :trigger, "Shadcn::Tooltip::Trigger::Component"
8
+ renders_one :tooltip_content, "Shadcn::Tooltip::Content::Component"
9
+
10
+ slot_name :tooltip
11
+
12
+ attr_reader :side, :align, :side_offset, :delay_duration
13
+
14
+ def initialize(side: :top, align: :center, side_offset: 0, delay_duration: 0, **attributes)
15
+ @side = side&.to_sym || :top
16
+ @align = align&.to_sym || :center
17
+ @side_offset = side_offset
18
+ @delay_duration = delay_duration
19
+ super(**attributes)
20
+ end
21
+
22
+ def element_attributes(**defaults)
23
+ super(**{
24
+ style: merged_style(CONTENTS_STYLE),
25
+ "data-controller" => "shadcn--tooltip",
26
+ "data-shadcn--tooltip-side-value" => side,
27
+ "data-shadcn--tooltip-align-value" => align,
28
+ "data-shadcn--tooltip-side-offset-value" => side_offset,
29
+ "data-shadcn--tooltip-delay-value" => delay_duration
30
+ }.merge(defaults))
31
+ end
32
+
33
+ def call
34
+ render_element(body: safe_join([ trigger, tooltip_content, content ].compact))
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Tooltip
5
+ module Content
6
+ # TooltipContent — includes the rotated square Radix renders as the arrow.
7
+ class Component < ApplicationViewComponent
8
+ ARROW_CLASSES = "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 " \
9
+ "rounded-[2px] bg-foreground fill-foreground"
10
+
11
+ slot_name :"tooltip-content"
12
+
13
+ style do
14
+ base {
15
+ "z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in " \
16
+ "rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background " \
17
+ "fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 " \
18
+ "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 " \
19
+ "data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out " \
20
+ "data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95"
21
+ }
22
+ end
23
+
24
+ def element_attributes(**defaults)
25
+ super(**{
26
+ role: "tooltip",
27
+ "data-state" => "closed",
28
+ hidden: true,
29
+ "data-shadcn--tooltip-target" => "content"
30
+ }.merge(defaults))
31
+ end
32
+
33
+ def call
34
+ render_element(body: safe_join([ content, arrow ]))
35
+ end
36
+
37
+ private
38
+
39
+ # Two elements, as Radix renders them: a wrapper that Popper pins to the
40
+ # side the content landed on, and inside it the rotated square shadcn
41
+ # styles. They cannot be one element — the classes set `rotate` and
42
+ # `translate`, which in Tailwind v4 are their own CSS properties and
43
+ # compose with `transform` rather than being overridden by it, so a
44
+ # placement written on the same element fights them instead of moving
45
+ # them. `popper.js` positions the wrapper.
46
+ #
47
+ # `display:block` on both, in a style attribute rather than a class,
48
+ # because upstream's arrow is an `<svg>` — a replaced element, which
49
+ # takes a width and a height where an inline `<span>` does not. Without
50
+ # it `size-2.5` applies to nothing and the arrow is 10px of intent and
51
+ # 0px of box: which is how this shipped, invisible however it was
52
+ # placed. A class would have to be one no vendored source carries, and
53
+ # `reverse_parity_spec` is there to notice that.
54
+ def arrow
55
+ tag.span("data-slot": "tooltip-arrow", style: "position:absolute;display:block") do
56
+ tag.span(class: ARROW_CLASSES, style: "display:block")
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Tooltip
5
+ class Preview < ApplicationViewComponentPreview
6
+ def default
7
+ render_with_template
8
+ end
9
+
10
+ # Upstream's "Side": the same tooltip on each of the four edges.
11
+ def sides
12
+ render_with_template
13
+ end
14
+
15
+ # Upstream's "With Keyboard Shortcut".
16
+ def with_keyboard_shortcut
17
+ render_with_template
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ <%= render(Shadcn::Tooltip::Component.new) do |t| %>
2
+ <% t.with_trigger(class: Shadcn::Button::Component.variant_classes(variant: :outline)) { "Hover me" } %>
3
+ <% t.with_tooltip_content { "Add to library" } %>
4
+ <% end %>
@@ -0,0 +1,19 @@
1
+ <%# Upstream's "Side": the same tooltip on each of the four edges, and the arrow
2
+ that points back at the trigger from each.
3
+
4
+ `side:` goes to the *root*, not to the content. Passed to
5
+ `with_tooltip_content` it is swallowed into the content's attributes and
6
+ quietly does nothing — which is what the first version of this preview did,
7
+ leaving all four asking for `top` and looking, correctly, identical.
8
+
9
+ Room on every side on purpose: the popper flips a tooltip that will not fit,
10
+ so a cramped page shows four tooltips on top and says nothing about `side:`
11
+ either. %>
12
+ <div class="flex h-72 items-center justify-center gap-16 px-24">
13
+ <% %i[top right bottom left].each do |side| %>
14
+ <%= render(Shadcn::Tooltip::Component.new(side:)) do |t| %>
15
+ <% t.with_trigger(class: Shadcn::Button::Component.variant_classes(variant: :outline)) { side.to_s.capitalize } %>
16
+ <% t.with_tooltip_content { "On the #{side}" } %>
17
+ <% end %>
18
+ <% end %>
19
+ </div>
@@ -0,0 +1,11 @@
1
+ <%# Upstream's "With Keyboard Shortcut": the tooltip carries the shortcut for
2
+ the thing it names, which is the case a `Kbd` inside one exists for. %>
3
+ <%= render(Shadcn::Tooltip::Component.new) do |t| %>
4
+ <% t.with_trigger(class: Shadcn::Button::Component.variant_classes(variant: :outline)) { "Save" } %>
5
+ <% t.with_tooltip_content do %>
6
+ <span class="flex items-center gap-2">
7
+ Save changes
8
+ <%= render(Shadcn::Kbd::Component.new) { "⌘S" } %>
9
+ </span>
10
+ <% end %>
11
+ <% end %>
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Tooltip
5
+ module Provider
6
+ # TooltipProvider — in React this only supplies the shared delay through
7
+ # context. Kept as a component so call sites read like the TSX; it renders
8
+ # nothing but its children.
9
+ class Component < ApplicationViewComponent
10
+ slot_name :"tooltip-provider"
11
+
12
+ attr_reader :delay_duration
13
+
14
+ def initialize(delay_duration: 0, **attributes)
15
+ @delay_duration = delay_duration
16
+ super(**attributes)
17
+ end
18
+
19
+ def element_attributes(**defaults)
20
+ super(**{
21
+ style: merged_style(CONTENTS_STYLE),
22
+ "data-delay-duration" => delay_duration
23
+ }.merge(defaults))
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Tooltip
5
+ module Trigger
6
+ # TooltipTrigger — opens on hover and on keyboard focus.
7
+ class Component < ApplicationViewComponent
8
+ default_tag :button
9
+ slot_name :"tooltip-trigger"
10
+
11
+ def element_attributes(**defaults)
12
+ super(**{
13
+ type: "button",
14
+ "data-state" => "closed",
15
+ "data-shadcn--tooltip-target" => "trigger",
16
+ "data-action" => "mouseenter->shadcn--tooltip#show " \
17
+ "mouseleave->shadcn--tooltip#hide " \
18
+ "focus->shadcn--tooltip#show " \
19
+ "blur->shadcn--tooltip#hide"
20
+ }.merge(defaults))
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnViewComponent
4
+ # `shadcn_form_with` is `form_with` with the shadcn builder already set, so
5
+ # the `f.shadcn_*` helpers are available without repeating `builder:`.
6
+ #
7
+ # <%= shadcn_form_with model: @user do |f| %>
8
+ # <%= f.shadcn_input_field :email, label: "Email", type: "email" %>
9
+ # <%= f.shadcn_submit "Save" %>
10
+ # <% end %>
11
+ #
12
+ # `form_with(..., builder: ShadcnViewComponent::FormBuilder)` does the same
13
+ # thing if you would rather be explicit.
14
+ module FormHelper
15
+ def shadcn_form_with(**options, &block)
16
+ form_with(**options.reverse_merge(builder: ShadcnViewComponent::FormBuilder), &block)
17
+ end
18
+
19
+ # For `fields_for` / `form_for` call sites that want the builder without the
20
+ # helper above.
21
+ def shadcn_form_builder
22
+ ShadcnViewComponent::FormBuilder
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnViewComponent
4
+ # View helpers for the theming system.
5
+ #
6
+ # <html>
7
+ # <head><%= shadcn_theme_script_tag %></head>
8
+ # <body class="<%= shadcn_theme_class %>">
9
+ #
10
+ # `shadcn_theme_script_tag` is what stops the flash of the wrong palette: it
11
+ # runs before the first paint, reads the stored preference and puts `.dark`
12
+ # and `data-shadcn-theme` on <html> itself, since <body> does not exist yet.
13
+ module ThemeHelper
14
+ MODE_COOKIE = "shadcn-ui-mode"
15
+ THEME_COOKIE = "shadcn-ui-theme"
16
+
17
+ # Emits the blocking snippet that applies the stored preference before the
18
+ # page paints. Put it in <head>, as early as possible.
19
+ #
20
+ # @param default_mode [String] "system", "light" or "dark"
21
+ # @param default_theme [String] a name from ShadcnViewComponent::Themes
22
+ def shadcn_theme_script_tag(default_mode: "system", default_theme: Themes::DEFAULT)
23
+ javascript_tag(nonce: true) do
24
+ raw(<<~JS)
25
+ (function () {
26
+ try {
27
+ var root = document.documentElement;
28
+ var mode = localStorage.getItem("#{MODE_COOKIE}") || "#{default_mode}";
29
+ var theme = localStorage.getItem("#{THEME_COOKIE}") || "#{default_theme}";
30
+ var dark = mode === "dark" ||
31
+ (mode === "system" && matchMedia("(prefers-color-scheme: dark)").matches);
32
+
33
+ root.classList.toggle("dark", dark);
34
+ root.style.colorScheme = dark ? "dark" : "light";
35
+ root.dataset.shadcnTheme = theme;
36
+ } catch (e) {}
37
+ })();
38
+ JS
39
+ end
40
+ end
41
+
42
+ # The colour theme this request should render with, from the cookie the
43
+ # store mirrors its `localStorage` value into. Lets a server-rendered page
44
+ # be correct on the very first byte.
45
+ def shadcn_theme_name(default: Themes::DEFAULT)
46
+ name = cookies[THEME_COOKIE].to_s
47
+ Themes.exists?(name) ? name : default
48
+ end
49
+
50
+ # The class to put on <body>.
51
+ def shadcn_theme_class(default: Themes::DEFAULT)
52
+ "theme-#{shadcn_theme_name(default:)}"
53
+ end
54
+
55
+ # The mode this request should render with: "light", "dark" or "system".
56
+ def shadcn_mode(default: "system")
57
+ mode = cookies[MODE_COOKIE].to_s
58
+ %w[light dark system].include?(mode) ? mode : default
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,180 @@
1
+ // Closing a layer has two halves that used to happen in one tick, which is why
2
+ // no `data-[state=closed]:animate-out` class in this library ever played.
3
+ //
4
+ // `data-state="closed"` and everything about *interaction* — the dismiss layer,
5
+ // aria, focus, the scroll lock — has to be immediate: a layer on its way out
6
+ // must not answer Escape. Taking the element out of the DOM has to wait, or the
7
+ // exit keyframes never get a frame.
8
+ //
9
+ // `getAnimations()` rather than an `animationend` listener. The event bubbles
10
+ // from descendants, so it would need filtering by both target and name; worse,
11
+ // it never fires when no animation applies, which forces a timeout — and that
12
+ // timeout then becomes the *normal* path in a host that has not loaded this
13
+ // gem's stylesheet. An empty animation list is unambiguous and needs no
14
+ // fallback.
15
+
16
+ // The animations worth holding a teardown for: the ones already under way that
17
+ // are also going to end.
18
+ //
19
+ // `playState === "running"` on its own is not that: an
20
+ // `animation-iteration-count: infinite` animation reports `"running"` and has
21
+ // no end to reach. Caller classes concatenate onto a component's own, so a host
22
+ // utility carrying one — `animate-pulse` among them — can land on closing
23
+ // content through supported API, and waiting on its `finished` leaves the layer
24
+ // in the document, painted and `pointer-events: none`, after everything that
25
+ // closes it has already run. Measured before this filter existed: an Escaped
26
+ // dialog still in the document five seconds later, `hidden` never set. An
27
+ // animation that never ends is not the exit either way, so nothing is lost by
28
+ // leaving it out. A timeout was the alternative: an arbitrary constant, and the
29
+ // layer would still be stuck for its length.
30
+ //
31
+ // `endTime` rather than `iterations` because it is the one number that answers
32
+ // "will this ever end" whatever put the animation there. Measured in headless
33
+ // Chrome: `Infinity` for both spellings — CSS `infinite` and
34
+ // `element.animate(…, { iterations: Infinity })` — and not a number at all for
35
+ // an animation on a `scroll()` timeline, which has no end on a clock either.
36
+ // `Number.isFinite` rejects all three.
37
+ const willEnd = (animation) =>
38
+ animation.playState === "running" &&
39
+ Number.isFinite(animation.effect?.getComputedTiming()?.endTime)
40
+
41
+ // A second literal, not a shared one: this selector is retyped to match
42
+ // `[data-slot][data-exiting]:not([data-slot="accordion-content"])` in
43
+ // shadcn.css, not read from it, so nothing in the code stops the two from
44
+ // drifting apart if one changes and the other does not. What actually catches
45
+ // that today is `exit_animation_spec.rb:426` — it probes a real control
46
+ // inside a collapsing panel for focus and hit-testing, which fails the same
47
+ // way whether the CSS exception goes missing or this one does.
48
+ //
49
+ // A collapsing accordion panel sits in the page flow with nothing behind it to
50
+ // protect from a click, which is what that CSS exception is for — and `inert` is
51
+ // a stronger tool than `pointer-events`, not an equivalent one: it drops the
52
+ // subtree out of hit-testing itself, so a control inside would neither focus nor
53
+ // receive a click no matter what its own `pointer-events` computed to. Applying
54
+ // `inert` here without the same carve-out would silently defeat the CSS rule's
55
+ // exception rather than agree with it. Measured with the carve-out missing: a
56
+ // button inside a collapsing panel read `pointer-events: auto`, was not
57
+ // focusable, and `elementFromPoint` at its centre returned the trigger instead.
58
+ const exemptFromInert = (element) => element.matches("[data-slot='accordion-content']")
59
+
60
+ // Holds teardowns that are waiting on an element's exit animation, one per
61
+ // element.
62
+ export class ExitQueue {
63
+ constructor() {
64
+ this.pending = new Map()
65
+ this.generation = 0
66
+ this.onBeforeCache = null
67
+ }
68
+
69
+ // Runs `teardown` once the animations already running on `element` have
70
+ // finished — or immediately and synchronously when there are none. That last
71
+ // part is the point: a host that never loaded this stylesheet, a host that
72
+ // overrode the classes away, and a host whose only animation here is an
73
+ // endless one all keep exactly today's behaviour.
74
+ //
75
+ // A second call for an element already waiting is ignored, so a `turbo:morph`
76
+ // re-render in the middle of an exit cannot queue the same teardown twice.
77
+ defer(element, teardown) {
78
+ if (this.pending.has(element)) return
79
+
80
+ const running = element.getAnimations().filter(willEnd)
81
+
82
+ if (!running.length) {
83
+ teardown()
84
+ return
85
+ }
86
+
87
+ // Marks the element as exiting for the length of the animation only — it
88
+ // is never rendered markup. Read by `[data-slot][data-exiting]` in
89
+ // shadcn.css, which stops most exiting elements intercepting clicks, as
90
+ // Radix does; that rule, not this marker, is what carries the accordion's
91
+ // exception.
92
+ element.dataset.exiting = ""
93
+
94
+ // `data-exiting` and `inert` are not two spellings of one thing: the
95
+ // marker above is a CSS hook, deliberately excluded for the accordion so
96
+ // its collapsing panel keeps taking clicks; `inert` takes the element out
97
+ // of the tab order and the accessibility tree instead, exempting the same
98
+ // accordion panel via `exemptFromInert` (above, and see its comment for
99
+ // how that exemption is kept in step with the CSS one) — `hidden` used to
100
+ // do both jobs, in the same tick, before either started waiting on this
101
+ // queue.
102
+ if (!exemptFromInert(element)) element.inert = true
103
+
104
+ // Tags this call's continuation with the generation it belongs to. Without
105
+ // it, cancel() followed by a fresh defer() in the same tick — reopen then
106
+ // close again before the first exit's promise has settled — would let the
107
+ // first continuation flush the second exit's teardown as soon as it drains,
108
+ // dismounting a layer whose new animation has barely started.
109
+ const generation = ++this.generation
110
+ this.pending.set(element, { teardown, generation })
111
+ this.watchTurbo()
112
+
113
+ // Reopening mid-exit cancels the animation, which rejects `finished`. Either
114
+ // way the waiting is over; whether the teardown still applies is decided by
115
+ // whoever calls `cancel`.
116
+ Promise.all(running.map((a) => a.finished))
117
+ .catch(() => {})
118
+ .then(() => {
119
+ if (this.pending.get(element)?.generation === generation) this.flush(element)
120
+ })
121
+ }
122
+
123
+ // Runs a pending teardown now. Called when the animation ends, and directly
124
+ // from `disconnect()`, where waiting is not an option: Turbo may be detaching
125
+ // the element, and a continuation would then be operating on a subtree that
126
+ // has left the document.
127
+ flush(element) {
128
+ const entry = this.pending.get(element)
129
+ if (!entry) return
130
+
131
+ this.pending.delete(element)
132
+ delete element.dataset.exiting
133
+ if (!exemptFromInert(element)) element.inert = false
134
+ this.unwatchTurbo()
135
+ entry.teardown()
136
+ }
137
+
138
+ // Drops a pending teardown without running it — the element is opening again,
139
+ // so there is nothing left to take out of the DOM.
140
+ cancel(element) {
141
+ if (!this.pending.delete(element)) return
142
+
143
+ delete element.dataset.exiting
144
+ if (!exemptFromInert(element)) element.inert = false
145
+ this.unwatchTurbo()
146
+ }
147
+
148
+ has(element) {
149
+ return this.pending.has(element)
150
+ }
151
+
152
+ flushAll() {
153
+ for (const element of [ ...this.pending.keys() ]) this.flush(element)
154
+ }
155
+
156
+ // A pending teardown must never survive into a Turbo snapshot. `cacheSnapshot()`
157
+ // clones whatever is in the document while handling `turbo:before-cache` — well
158
+ // inside an exit animation's duration — so without this a layer closed by the
159
+ // same pointerdown that starts a Drive navigation gets cached mid-exit: wrapper,
160
+ // placeholder and `data-exiting` all cloned in. A later restore built from that
161
+ // clone would then nest a second wrapper the next time the layer opens, and the
162
+ // surviving `data-exiting` would leave it unclickable.
163
+ //
164
+ // One listener per queue, added only while something is pending and removed
165
+ // as soon as nothing is, rather than one at module load: most pages never
166
+ // have an exit in flight when they navigate away.
167
+ watchTurbo() {
168
+ if (this.onBeforeCache) return
169
+
170
+ this.onBeforeCache = () => this.flushAll()
171
+ document.addEventListener("turbo:before-cache", this.onBeforeCache)
172
+ }
173
+
174
+ unwatchTurbo() {
175
+ if (this.pending.size || !this.onBeforeCache) return
176
+
177
+ document.removeEventListener("turbo:before-cache", this.onBeforeCache)
178
+ this.onBeforeCache = null
179
+ }
180
+ }