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,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module ThemeSelector
5
+ # Port of apps/v4/components/theme-selector.tsx — the select that swaps the
6
+ # colour palette.
7
+ #
8
+ # In React this reads `useThemeConfig()`; here each option hands its value
9
+ # to the `shadcn--theme` Stimulus controller, which swaps the `theme-*`
10
+ # class the way shadcn's `ActiveThemeProvider` does.
11
+ class Component < ApplicationViewComponent
12
+ slot_name :"theme-selector"
13
+
14
+ style do
15
+ base { "flex items-center gap-2" }
16
+ end
17
+
18
+ attr_reader :themes, :value
19
+
20
+ # @param themes [Array<Hash>] entries from ShadcnViewComponent::Themes
21
+ # @param value [String] the palette currently applied
22
+ def initialize(themes: ShadcnViewComponent::Themes::BASE_COLORS,
23
+ value: ShadcnViewComponent::Themes::DEFAULT,
24
+ label: nil,
25
+ **attributes)
26
+ @themes = themes
27
+ @value = value.to_s
28
+ @label = label
29
+ super(**attributes)
30
+ end
31
+
32
+ def label
33
+ @label || shadcn_t("theme.label")
34
+ end
35
+
36
+ def label_id
37
+ @label_id ||= "shadcn-theme-selector-#{SecureRandom.hex(4)}"
38
+ end
39
+
40
+ def element_attributes(**defaults)
41
+ super(**{ "data-controller" => "shadcn--theme" }.merge(defaults))
42
+ end
43
+
44
+ def call
45
+ render_element(body: safe_join([ hidden_label, select ]))
46
+ end
47
+
48
+ private
49
+
50
+ def hidden_label
51
+ render(Label::Component.new(id: label_id, class: "sr-only")) { label }
52
+ end
53
+
54
+ def select
55
+ render(Select::Component.new(value:, placeholder: "Select a theme")) do |component|
56
+ component.with_trigger(
57
+ "aria-labelledby" => label_id,
58
+ class: "w-36",
59
+ placeholder: current_title.nil?
60
+ ) do |trigger|
61
+ trigger.with_value(placeholder: "Select a theme") { current_title }
62
+ end
63
+ component.with_select_content { options }
64
+ end
65
+ end
66
+
67
+ def current_title
68
+ ShadcnViewComponent::Themes.find(value)&.fetch(:title)
69
+ end
70
+
71
+ def options
72
+ render(Select::Group::Component.new) do
73
+ safe_join([ render(Select::Label::Component.new) { label } ] + items)
74
+ end
75
+ end
76
+
77
+ def items
78
+ themes.map do |theme|
79
+ render(Select::Item::Component.new(
80
+ value: theme[:name],
81
+ selected: theme[:name] == value,
82
+ "data-action": "click->shadcn--theme#setTheme"
83
+ )) { theme[:title] }
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module ThemeSelector
5
+ class Preview < ApplicationViewComponentPreview
6
+ # The base colours a project picks from.
7
+ def default
8
+ render_with_template
9
+ end
10
+
11
+ # Every palette in the registry, accents included.
12
+ def all_themes
13
+ render_with_template
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ <div class="flex flex-col gap-6">
2
+ <%= render(Shadcn::ThemeSelector::Component.new(
3
+ themes: ShadcnViewComponent::Themes::ALL,
4
+ value: shadcn_theme_name,
5
+ label: "Palette"
6
+ )) %>
7
+
8
+ <div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
9
+ <% ShadcnViewComponent::Themes::ALL.each do |theme| %>
10
+ <div class="theme-<%= theme[:name] %> flex flex-col gap-2 rounded-lg border bg-card p-3">
11
+ <span class="text-xs font-medium text-card-foreground"><%= theme[:title] %></span>
12
+ <div class="flex gap-1">
13
+ <% %w[bg-primary bg-secondary bg-muted bg-accent bg-destructive].each do |swatch| %>
14
+ <span class="size-5 rounded-sm border <%= swatch %>"></span>
15
+ <% end %>
16
+ </div>
17
+ </div>
18
+ <% end %>
19
+ </div>
20
+ </div>
@@ -0,0 +1,18 @@
1
+ <div class="flex flex-col gap-6">
2
+ <%= render(Shadcn::ThemeSelector::Component.new(value: shadcn_theme_name)) %>
3
+
4
+ <%= render(Shadcn::Card::Component.new(class: "w-full max-w-sm")) do |card| %>
5
+ <% card.with_header do |header| %>
6
+ <% header.with_title { "Pick a palette" } %>
7
+ <% header.with_description { "The theme-* class on <body> re-points every token." } %>
8
+ <% end %>
9
+ <% card.with_card_content do %>
10
+ <div class="flex flex-wrap gap-2">
11
+ <%= render(Shadcn::Button::Component.new) { "Primary" } %>
12
+ <%= render(Shadcn::Button::Component.new(variant: :secondary)) { "Secondary" } %>
13
+ <%= render(Shadcn::Button::Component.new(variant: :destructive)) { "Destructive" } %>
14
+ <%= render(Shadcn::Badge::Component.new) { "Badge" } %>
15
+ </div>
16
+ <% end %>
17
+ <% end %>
18
+ </div>
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Toaster
5
+ # Ours, not a port — and the distinction matters more here than anywhere
6
+ # else in this gem.
7
+ #
8
+ # `sonner.tsx` is forty lines and emits no `data-slot` at all: a theme, five
9
+ # icons and four custom properties handed to the `sonner` package, which
10
+ # supplies every element and every rule — 1,601 lines of TypeScript and 729
11
+ # of CSS. There is no upstream markup here to be 1:1 with, so `parity_spec`
12
+ # has nothing to check and this component's shape is this port's own.
13
+ #
14
+ # What is kept from upstream is what can be: sonner's measurements, read
15
+ # from its stylesheet — 356px wide, 14px between, 24px from the edge, four
16
+ # seconds of life — and the four colours `sonner.tsx` itself chooses, which
17
+ # are the popover's.
18
+ #
19
+ # And the way in is Rails': a toast is a component, so the server can raise
20
+ # one. Three routes, in order of how much an app will use them —
21
+ #
22
+ # flash `Toaster::Component.new(flash:)` turns each into a toast
23
+ # turbo_stream `turbo_stream.append "shadcn-toasts", …` onto this list
24
+ # javascript `document.dispatchEvent(new CustomEvent("shadcn--toast", …))`
25
+ #
26
+ # — where sonner has only the third. See features/toaster.md.
27
+ class Component < ApplicationViewComponent
28
+ POSITIONS = {
29
+ "top-left" => "top-6 left-6 items-start",
30
+ "top-center" => "top-6 left-1/2 -translate-x-1/2 items-center",
31
+ "top-right" => "top-6 right-6 items-end",
32
+ "bottom-left" => "bottom-6 left-6 items-start",
33
+ "bottom-center" => "bottom-6 left-1/2 -translate-x-1/2 items-center",
34
+ "bottom-right" => "bottom-6 right-6 items-end"
35
+ }.freeze
36
+
37
+ # sonner's own, so a caller who has read its docs is not surprised.
38
+ DEFAULT_DURATION = 4000
39
+
40
+ renders_many :toasts, "Shadcn::Toaster::Toast::Component"
41
+
42
+ # A `<section>`, as sonner renders (index.tsx:788): `aria-label` on a bare
43
+ # `<div>` is prohibited ARIA — a label needs a role to belong to, and a
44
+ # section is the landmark that has one.
45
+ default_tag :section
46
+ slot_name :toaster
47
+
48
+ attr_reader :position, :duration, :list_id, :flash
49
+
50
+ def initialize(position: "bottom-right", duration: DEFAULT_DURATION,
51
+ list_id: "shadcn-toasts", flash: nil, **attributes)
52
+ @position = POSITIONS.key?(position.to_s) ? position.to_s : "bottom-right"
53
+ @duration = duration
54
+ @list_id = list_id
55
+ @flash = flash
56
+ super(**attributes)
57
+ end
58
+
59
+ def element_attributes(**defaults)
60
+ super(**{
61
+ class: "group/toaster pointer-events-none fixed z-[100] flex " \
62
+ "#{POSITIONS.fetch(position)}",
63
+ "aria-live" => "polite",
64
+ # sonner's own (index.tsx:792): each toast is read on its own, not as
65
+ # a re-reading of the whole stack every time one arrives.
66
+ "aria-atomic" => "false",
67
+ "aria-label" => shadcn_t("toaster.label"),
68
+ "data-position" => position,
69
+ "data-controller" => "shadcn--toaster",
70
+ "data-shadcn--toaster-duration-value" => duration,
71
+ "data-action" => "shadcn--toast@document->shadcn--toaster#raise"
72
+ }.merge(defaults))
73
+ end
74
+
75
+ def call
76
+ render_element(body: safe_join([ list, template ]))
77
+ end
78
+
79
+ private
80
+
81
+ # The element a `turbo_stream.append` aims at, which is why it has an id
82
+ # by default and why the id is an argument rather than generated.
83
+ def list
84
+ tag.ol(safe_join([ flash_toasts, toasts, content ].flatten.compact),
85
+ id: list_id,
86
+ # The stack, and it is a stack rather than a column: the toasts
87
+ # are absolutely placed inside this box and the controller writes
88
+ # where each one sits. `w-[356px]` is sonner's width, and the
89
+ # height is the controller's — it is what the pointer has to be
90
+ # inside for the stack to open.
91
+ # No transition on the height, and that is deliberate: this box is
92
+ # the area a pointer has to be inside for the stack to stay open,
93
+ # and an area that arrives four hundred milliseconds late is an
94
+ # area that is wrong while it matters. The toasts animate; the
95
+ # box they are in does not. Measured in a browser that runs no
96
+ # animation frames, a transition here left it at zero for good.
97
+ class: "relative w-[356px] max-w-[calc(100vw-2rem)]",
98
+ "data-shadcn--toaster-target": "list",
99
+ "data-action": "pointerenter->shadcn--toaster#hold " \
100
+ "pointerleave->shadcn--toaster#release " \
101
+ "focusin->shadcn--toaster#hold " \
102
+ "focusout->shadcn--toaster#release")
103
+ end
104
+
105
+ # Rails' own flash, rendered with the page. `:notice` and `:alert` are the
106
+ # two `redirect_to` sets without being asked, so they are mapped rather
107
+ # than left to be configured.
108
+ FLASH_VARIANTS = { "notice" => :success, "alert" => :error, "error" => :error,
109
+ "warning" => :warning, "info" => :info }.freeze
110
+
111
+ def flash_toasts
112
+ return [] if flash.blank?
113
+
114
+ flash.map do |key, message|
115
+ next if message.blank?
116
+
117
+ render(Toast::Component.new(title: message, variant: FLASH_VARIANTS.fetch(key.to_s, :default)))
118
+ end.compact
119
+ end
120
+
121
+ # The shapes the JavaScript route clones — one per variant, because a
122
+ # variant is not only an attribute: `success` has an icon and `default`
123
+ # has none, and no attribute set afterwards can conjure an element that
124
+ # was never rendered. That is what the first version of this did, and the
125
+ # toasts it made came out blank-faced.
126
+ #
127
+ # Rendered in Ruby so the class strings live in one place. A controller
128
+ # that builds this markup from string literals is a second copy of every
129
+ # class in this file, kept in step by hand.
130
+ def template
131
+ safe_join(Toast::Component::VARIANTS.keys.map { |variant|
132
+ tag.template("data-shadcn--toaster-target": "template", "data-variant": variant) do
133
+ render(Toast::Component.new(title: "", description: "", variant:))
134
+ end
135
+ })
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Toaster
5
+ class Preview < ApplicationViewComponentPreview
6
+ def default
7
+ render_with_template
8
+ end
9
+
10
+ # Rendered with the page, which is what a Rails flash does.
11
+ def from_the_server
12
+ render_with_template
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ <%# The JavaScript route: an event on the document, which the toaster hears
2
+ wherever it is on the page. `sonner`'s `toast()` is a function you import;
3
+ this is an event, because a Rails app has no bundler step to import into. %>
4
+ <div class="flex flex-wrap items-center gap-2">
5
+ <% [["default", "Event created"],
6
+ ["success", "Changes saved"],
7
+ ["info", "A new version is available"],
8
+ ["warning", "Your trial ends on Friday"],
9
+ ["error", "Could not save changes"]].each do |variant, message| %>
10
+ <%= render(Shadcn::Button::Component.new(
11
+ variant: :outline,
12
+ onclick: "document.dispatchEvent(new CustomEvent('shadcn--toast', { detail: " \
13
+ "{ title: #{message.to_json}, description: 'Sunday, December 03, 2023 at 9:00 AM', " \
14
+ "variant: #{variant.to_json} } }))"
15
+ )) { variant.capitalize } %>
16
+ <% end %>
17
+ </div>
18
+
19
+ <%= render(Shadcn::Toaster::Component.new) %>
@@ -0,0 +1,15 @@
1
+ <%# The two routes sonner does not have.
2
+
3
+ Rendered with the page — which is what `Toaster::Component.new(flash:)` does
4
+ with a Rails flash, mapping `:notice` and `:alert` to the two variants
5
+ `redirect_to` produces without being asked — and appended afterwards, which
6
+ is what a `turbo_stream.append "shadcn-toasts"` does. Both arrive as ordinary
7
+ elements in the list, and the controller starts their clocks because it
8
+ watches the list rather than being told. %>
9
+ <%= render(Shadcn::Toaster::Component.new(position: "top-center", duration: 0)) do |t| %>
10
+ <% t.with_toast(title: "Rendered with the page", description: "A flash, or anything else the server knows.", variant: :success) %>
11
+ <% t.with_toast(title: "Appended afterwards", description: "A turbo_stream.append onto this same list.", variant: :info) do |toast| %>
12
+ <% toast.with_action(variant: :outline, size: :sm) { "Undo" } %>
13
+ <% end %>
14
+ <% t.with_toast(title: "Still working", description: "A loading toast keeps no clock at all.", variant: :loading) %>
15
+ <% end %>
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Toaster
5
+ module Toast
6
+ # One toast.
7
+ #
8
+ # A real component rather than a shape the JavaScript builds, because the
9
+ # most useful way to raise one in a Rails app is from the server: a flash
10
+ # rendered with the page, or a `turbo_stream.append` onto the list. The
11
+ # controller then only has to start its clock and take it away again.
12
+ class Component < ApplicationViewComponent
13
+ VARIANTS = {
14
+ default: nil,
15
+ success: "circle-check",
16
+ info: "info",
17
+ warning: "triangle-alert",
18
+ error: "octagon-x",
19
+ loading: "loader-circle"
20
+ }.freeze
21
+
22
+ default_tag :li
23
+ slot_name :toast
24
+
25
+ style do
26
+ base {
27
+ # sonner's own measurements, read from its stylesheet: 356px wide,
28
+ # 16px of padding, a 13px face and a soft shadow. The colours are
29
+ # the four custom properties `sonner.tsx` sets — popover, its
30
+ # foreground, the border and the radius — written here as the
31
+ # Tailwind tokens they point at.
32
+ # Absolutely placed: the toasts are a *stack*, one behind another,
33
+ # and the controller writes where each sits. A column would be
34
+ # simpler and would not be this component — the fan-out on hover is
35
+ # what a toaster is.
36
+ #
37
+ # The movement is a transition rather than a keyframe animation, as
38
+ # sonner's is: a toast moves whenever the one in front of it goes
39
+ # away, and an animation would have to be restarted each time where
40
+ # a transition simply follows.
41
+ "pointer-events-auto absolute right-0 flex w-full items-start gap-3 overflow-hidden " \
42
+ "rounded-lg border bg-popover p-4 text-[13px] text-popover-foreground " \
43
+ "shadow-[0_4px_12px_rgba(0,0,0,0.1)] transition-all duration-[400ms] " \
44
+ "data-[state=closed]:opacity-0"
45
+ }
46
+
47
+ variants {
48
+ variant {
49
+ default { "" }
50
+ success { "[&_[data-slot=toast-icon]]:text-emerald-600 dark:[&_[data-slot=toast-icon]]:text-emerald-400" }
51
+ info { "[&_[data-slot=toast-icon]]:text-blue-600 dark:[&_[data-slot=toast-icon]]:text-blue-400" }
52
+ warning { "[&_[data-slot=toast-icon]]:text-amber-600 dark:[&_[data-slot=toast-icon]]:text-amber-400" }
53
+ error { "[&_[data-slot=toast-icon]]:text-destructive" }
54
+ loading { "[&_[data-slot=toast-icon]]:animate-spin" }
55
+ }
56
+ }
57
+
58
+ defaults { { variant: :default } }
59
+ end
60
+
61
+ renders_one :action, "Shadcn::Button::Component"
62
+
63
+ attr_reader :variant, :title, :description, :duration, :dismissible
64
+
65
+ def initialize(title: nil, description: nil, variant: :default, duration: nil,
66
+ dismissible: true, **attributes)
67
+ @variant = VARIANTS.key?(variant&.to_sym) ? variant.to_sym : :default
68
+ @title = title
69
+ @description = description
70
+ @duration = duration
71
+ @dismissible = dismissible
72
+ super(**attributes)
73
+ end
74
+
75
+ def style_variants = { variant: }
76
+
77
+ def element_attributes(**defaults)
78
+ # No `role` here, and sonner sets none either (index.tsx renders a bare
79
+ # `<li>`): a role on an `<li>` replaces its implicit `listitem`, and
80
+ # an `<ol>` whose children are not list items is a list axe fails and
81
+ # a screen reader miscounts. The announcement comes from the region's
82
+ # `aria-live`, which is where sonner puts it too (index.tsx:790).
83
+ super(**{
84
+ "data-variant" => variant,
85
+ "data-state" => "open",
86
+ "data-duration" => duration,
87
+ "data-shadcn--toaster-target" => "toast"
88
+ }.compact.merge(defaults))
89
+ end
90
+
91
+ def call
92
+ render_element(body: safe_join([ icon, body, dismiss ].compact))
93
+ end
94
+
95
+ private
96
+
97
+ def icon
98
+ name = VARIANTS[variant]
99
+ return unless name
100
+
101
+ render(Icon::Component.new(name, class: "size-4 shrink-0", "data-slot": "toast-icon"))
102
+ end
103
+
104
+ def body
105
+ tag.div(class: "flex min-w-0 flex-1 flex-col gap-1") do
106
+ safe_join([
107
+ (tag.div(title, class: "font-medium", "data-slot": "toast-title") if title),
108
+ (tag.div(description, class: "text-muted-foreground", "data-slot": "toast-description") if description),
109
+ (tag.div(action, class: "pt-1") if action?),
110
+ content
111
+ ].compact)
112
+ end
113
+ end
114
+
115
+ def dismiss
116
+ return unless dismissible
117
+
118
+ # Always visible, where sonner reveals its own on hover. sonner can
119
+ # afford to hide it because a toast there is also dismissed by
120
+ # swiping it away; this port has no swipe, so the button is the only
121
+ # way out — and a control that appears on hover is not a control on a
122
+ # phone.
123
+ tag.button(
124
+ type: "button",
125
+ class: "absolute top-2 right-2 rounded-sm text-muted-foreground transition-colors " \
126
+ "hover:text-foreground",
127
+ "aria-label": shadcn_t("toaster.dismiss"),
128
+ "data-action": "click->shadcn--toaster#dismiss"
129
+ ) { render(Icon::Component.new("x", class: "size-4")) }
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Toggle
5
+ # Port of registry/new-york-v4/ui/toggle.tsx
6
+ class Component < ApplicationViewComponent
7
+ default_tag :button
8
+ slot_name :toggle
9
+
10
+ style do
11
+ base {
12
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium " \
13
+ "whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted " \
14
+ "hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] " \
15
+ "focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 " \
16
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20 " \
17
+ "data-[state=on]:bg-accent data-[state=on]:text-accent-foreground " \
18
+ "dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none " \
19
+ "[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
20
+ }
21
+
22
+ variants {
23
+ variant {
24
+ default { "bg-transparent" }
25
+ outline {
26
+ "border border-input bg-transparent shadow-xs hover:bg-accent " \
27
+ "hover:text-accent-foreground"
28
+ }
29
+ }
30
+
31
+ size {
32
+ default { "h-9 min-w-9 px-2" }
33
+ sm { "h-8 min-w-8 px-1.5" }
34
+ lg { "h-10 min-w-10 px-2.5" }
35
+ }
36
+ }
37
+
38
+ defaults { { variant: :default, size: :default } }
39
+ end
40
+
41
+ # Exposed the way shadcn exports `toggleVariants`, for ToggleGroupItem.
42
+ def self.variant_classes(variant: :default, size: :default, class: nil)
43
+ style_config.compile(
44
+ default_style_name.to_sym,
45
+ variant: variant&.to_sym || :default,
46
+ size: size&.to_sym || :default,
47
+ class: binding.local_variable_get(:class)
48
+ )
49
+ end
50
+
51
+ attr_reader :variant, :size, :pressed, :disabled
52
+
53
+ def initialize(variant: :default, size: :default, pressed: false, disabled: false,
54
+ **attributes)
55
+ @variant = variant&.to_sym || :default
56
+ @size = size&.to_sym || :default
57
+ @pressed = pressed
58
+ @disabled = disabled
59
+ super(**attributes)
60
+ end
61
+
62
+ def style_variants
63
+ { variant:, size: }
64
+ end
65
+
66
+ def element_attributes(**defaults)
67
+ super(**{
68
+ type: "button",
69
+ disabled: (true if disabled),
70
+ "aria-pressed" => pressed.to_s,
71
+ "data-state" => (pressed ? "on" : "off"),
72
+ "data-disabled" => (true if disabled),
73
+ "data-controller" => "shadcn--toggle",
74
+ "data-shadcn--toggle-pressed-value" => pressed,
75
+ "data-shadcn--toggle-disabled-value" => disabled,
76
+ "data-action" => "shadcn--toggle#toggle"
77
+ }.merge(defaults))
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Toggle
5
+ class Preview < ApplicationViewComponentPreview
6
+ def default
7
+ render_with_template
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ <div class="flex items-center gap-2">
2
+ <%= render(Shadcn::Toggle::Component.new) { "Default" } %>
3
+ <%= render(Shadcn::Toggle::Component.new(variant: :outline, pressed: true)) { "Outline, on" } %>
4
+ <%= render(Shadcn::Toggle::Component.new(size: :sm)) { "Small" } %>
5
+ <%= render(Shadcn::Toggle::Component.new(size: :lg, disabled: true)) { "Disabled" } %>
6
+ </div>
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module ToggleGroup
5
+ # Port of registry/new-york-v4/ui/toggle-group.tsx
6
+ #
7
+ # React passes variant/size/spacing down through context; here the group
8
+ # exposes them to its items through the slot, which is the same idea.
9
+ class Component < ApplicationViewComponent
10
+ include Concerns::SubmitsValue
11
+ renders_many :items, ->(**kwargs, &block) {
12
+ Item::Component.new(variant:, size:, spacing:, **kwargs, &block)
13
+ }
14
+
15
+ slot_name :"toggle-group"
16
+
17
+ style do
18
+ base {
19
+ "group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md " \
20
+ "data-[spacing=default]:data-[variant=outline]:shadow-xs"
21
+ }
22
+ end
23
+
24
+ attr_reader :type, :variant, :size, :spacing, :value, :name
25
+
26
+ def initialize(type: :single, variant: :default, size: :default, spacing: 0,
27
+ value: nil, name: nil, **attributes)
28
+ @type = type&.to_sym || :single
29
+ @variant = variant&.to_sym || :default
30
+ @size = size&.to_sym || :default
31
+ @spacing = spacing
32
+ @value = Array(value).compact.join(",")
33
+ @name = name
34
+ super(**attributes)
35
+ end
36
+
37
+ def element_attributes(**defaults)
38
+ super(**{
39
+ "data-variant" => variant,
40
+ "data-size" => size,
41
+ "data-spacing" => spacing,
42
+ style: [ attributes[:style], "--gap: #{spacing}" ].compact.join(";"),
43
+ "data-controller" => "shadcn--toggle-group",
44
+ "data-shadcn--toggle-group-type-value" => type,
45
+ "data-shadcn--toggle-group-value-value" => value
46
+ }.merge(defaults))
47
+ end
48
+
49
+ def call
50
+ render_element(body: safe_join([ items, content, hidden_input ].flatten.compact))
51
+ end
52
+
53
+ private
54
+
55
+ def hidden_input
56
+ return unless name
57
+
58
+ tag.input(**hidden_input_attributes("data-shadcn--toggle-group-target" => "input"))
59
+ end
60
+ end
61
+ end
62
+ end