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,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module InputGroup
5
+ class Preview < ApplicationViewComponentPreview
6
+ def default
7
+ render_with_template
8
+ end
9
+
10
+ # Upstream's "Icon", "Kbd", "Dropdown" and "Spinner" — what an addon holds,
11
+ # where the default preview shows where an addon sits.
12
+ def addons
13
+ render_with_template
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,53 @@
1
+ <%# Upstream's "Icon", "Kbd", "Dropdown" and "Spinner": four things an addon
2
+ holds that the default preview does not show, which has text, buttons and a
3
+ textarea.
4
+
5
+ An addon is a slot for anything — its only argument is `align:`, and the
6
+ default preview covers all four of those. What changes here is what goes
7
+ inside one. %>
8
+ <div class="flex w-full max-w-md flex-col gap-6">
9
+ <%# An icon, which is the commonest case and the reason the addon has no
10
+ padding of its own. %>
11
+ <%= render(Shadcn::InputGroup::Component.new) do %>
12
+ <%= render(Shadcn::InputGroup::Addon::Component.new) do %>
13
+ <%= render(Shadcn::Icon::Component.new("search")) %>
14
+ <% end %>
15
+ <%= render(Shadcn::InputGroup::Input::Component.new(placeholder: "Search", "aria-label": "Search")) %>
16
+ <% end %>
17
+
18
+ <%# A shortcut on the trailing edge, which is what `Kbd` is for. %>
19
+ <%= render(Shadcn::InputGroup::Component.new) do %>
20
+ <%= render(Shadcn::InputGroup::Input::Component.new(placeholder: "Search commands", "aria-label": "Search commands")) %>
21
+ <%= render(Shadcn::InputGroup::Addon::Component.new(align: :"inline-end")) do %>
22
+ <%= render(Shadcn::Kbd::Group::Component.new) do %>
23
+ <%= render(Shadcn::Kbd::Component.new) { "⌘" } %>
24
+ <%= render(Shadcn::Kbd::Component.new) { "K" } %>
25
+ <% end %>
26
+ <% end %>
27
+ <% end %>
28
+
29
+ <%# A menu hanging off the addon's own button. %>
30
+ <%= render(Shadcn::InputGroup::Component.new) do %>
31
+ <%= render(Shadcn::InputGroup::Input::Component.new(placeholder: "you@example.com", "aria-label": "Email")) %>
32
+ <%= render(Shadcn::InputGroup::Addon::Component.new(align: :"inline-end")) do %>
33
+ <%= render(Shadcn::DropdownMenu::Component.new) do |m| %>
34
+ <% m.with_trigger(as: :button, class: "inline-flex items-center gap-1 text-sm text-muted-foreground") do %>
35
+ Work
36
+ <%= render(Shadcn::Icon::Component.new("chevron-down")) %>
37
+ <% end %>
38
+ <% m.with_menu_content(class: "w-56") do %>
39
+ <%= render(Shadcn::DropdownMenu::Item::Component.new) { "Work" } %>
40
+ <%= render(Shadcn::DropdownMenu::Item::Component.new) { "Personal" } %>
41
+ <% end %>
42
+ <% end %>
43
+ <% end %>
44
+ <% end %>
45
+
46
+ <%# A spinner, for a field that is checking something as you type. %>
47
+ <%= render(Shadcn::InputGroup::Component.new) do %>
48
+ <%= render(Shadcn::InputGroup::Input::Component.new(value: "octocat", "aria-label": "Username")) %>
49
+ <%= render(Shadcn::InputGroup::Addon::Component.new(align: :"inline-end")) do %>
50
+ <%= render(Shadcn::Spinner::Component.new) %>
51
+ <% end %>
52
+ <% end %>
53
+ </div>
@@ -0,0 +1,25 @@
1
+ <div class="flex w-full max-w-md flex-col gap-6">
2
+ <%= render(Shadcn::InputGroup::Component.new) do %>
3
+ <%= render(Shadcn::InputGroup::Addon::Component.new) do %>
4
+ <%= render(Shadcn::InputGroup::Text::Component.new) { "https://" } %>
5
+ <% end %>
6
+ <%= render(Shadcn::InputGroup::Input::Component.new(placeholder: "example.com")) %>
7
+ <% end %>
8
+
9
+ <%= render(Shadcn::InputGroup::Component.new) do %>
10
+ <%= render(Shadcn::InputGroup::Input::Component.new(placeholder: "Search")) %>
11
+ <%= render(Shadcn::InputGroup::Addon::Component.new(align: :"inline-end")) do %>
12
+ <%= render(Shadcn::InputGroup::Button::Component.new) { "Go" } %>
13
+ <% end %>
14
+ <% end %>
15
+
16
+ <%= render(Shadcn::InputGroup::Component.new) do %>
17
+ <%= render(Shadcn::InputGroup::Addon::Component.new(align: :"block-start")) do %>
18
+ <%= render(Shadcn::InputGroup::Text::Component.new) { "Describe the issue" } %>
19
+ <% end %>
20
+ <%= render(Shadcn::InputGroup::Textarea::Component.new(placeholder: "What happened?")) %>
21
+ <%= render(Shadcn::InputGroup::Addon::Component.new(align: :"block-end")) do %>
22
+ <%= render(Shadcn::InputGroup::Button::Component.new(size: :sm)) { "Submit" } %>
23
+ <% end %>
24
+ <% end %>
25
+ </div>
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module InputGroup
5
+ module Text
6
+ # InputGroupText. A `<span>`, and like ButtonGroupText it carries no
7
+ # `data-slot` upstream, so it is not given one here.
8
+ class Component < ApplicationViewComponent
9
+ default_tag :span
10
+
11
+ style do
12
+ base {
13
+ "flex items-center gap-2 text-sm text-muted-foreground " \
14
+ "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4"
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module InputGroup
5
+ module Textarea
6
+ # InputGroupTextarea renders the ported Textarea, and shares
7
+ # `data-slot="input-group-control"` with InputGroupInput — see that file
8
+ # for why the two must not be told apart.
9
+ class Component < Shadcn::Textarea::Component
10
+ slot_name :"input-group-control"
11
+
12
+ EXTRA_CLASSES = "flex-1 resize-none rounded-none border-0 bg-transparent py-3 " \
13
+ "shadow-none focus-visible:ring-0 dark:bg-transparent"
14
+
15
+ def css_classes(extra = nil)
16
+ super([ EXTRA_CLASSES, extra ].compact.join(" "))
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module InputOtp
5
+ # Port of registry/new-york-v4/ui/input-otp.tsx
6
+ #
7
+ # Upstream builds this on the **input-otp** package, and this port does not.
8
+ # Measured: of its 715 lines, `input-otp.tsx` uses the root component and,
9
+ # per box, three values — `char`, `isActive` and `hasFakeCaret`. The rule
10
+ # behind them is six lines (input.tsx:596-618), and the mechanism under it
11
+ # is one real `<input>` laid invisibly over the boxes, which is what makes
12
+ # typing, paste, backspace, the arrow keys and a one-time-code autofill the
13
+ # browser's problem rather than ours.
14
+ #
15
+ # What the 715 lines mostly are is the part that cannot be ported by
16
+ # reading: 21 of them name Safari, iOS, a password manager or a selection
17
+ # quirk. features/input-otp.md says which of those this port answers and
18
+ # which it does not.
19
+ #
20
+ # The shape is upstream's, read from the rendered demo rather than from the
21
+ # TSX, which shows none of it: the boxes come first, and the input sits over
22
+ # them in an absolutely positioned layer.
23
+ class Component < ApplicationViewComponent
24
+ slot_name :"input-otp-container"
25
+
26
+ style do
27
+ base { "flex items-center gap-2 has-disabled:opacity-50" }
28
+ end
29
+
30
+ # Everything the input needs to be a real one. `disabled:cursor-not-allowed`
31
+ # is upstream's class for it; the rest of its look is a rule in
32
+ # `shadcn.css`, because it is a technique rather than a style — see there.
33
+ INPUT_CLASSES = "disabled:cursor-not-allowed"
34
+
35
+ attr_reader :max_length, :name, :value, :disabled, :pattern, :autofocus, :input_attributes
36
+
37
+ def initialize(max_length: 6, name: nil, value: nil, disabled: false, pattern: nil,
38
+ autofocus: false, input: {}, **attributes)
39
+ @max_length = max_length.to_i
40
+ @name = name
41
+ @value = value.to_s
42
+ @disabled = disabled
43
+ @pattern = pattern
44
+ @autofocus = autofocus
45
+ @input_attributes = input
46
+ super(**attributes)
47
+ end
48
+
49
+ def element_attributes(**defaults)
50
+ super(**{
51
+ style: merged_style("position:relative"),
52
+ "data-controller" => "shadcn--input-otp",
53
+ "data-shadcn--input-otp-length-value" => max_length
54
+ }.merge(defaults))
55
+ end
56
+
57
+ def call
58
+ render_element(body: safe_join([ content, overlay ]))
59
+ end
60
+
61
+ private
62
+
63
+ # Upstream's own arrangement: the boxes are the page, and the input is a
64
+ # layer over them that takes the pointer while the layer itself does not.
65
+ def overlay
66
+ tag.div(input, style: "position:absolute;inset:0;pointer-events:none")
67
+ end
68
+
69
+ def input
70
+ tag.input(**{
71
+ "data-slot": "input-otp",
72
+ "data-shadcn--input-otp-target": "input",
73
+ "data-action": "input->shadcn--input-otp#entered " \
74
+ "select->shadcn--input-otp#refresh " \
75
+ "keyup->shadcn--input-otp#refresh " \
76
+ "click->shadcn--input-otp#refresh " \
77
+ "focus->shadcn--input-otp#focus " \
78
+ "blur->shadcn--input-otp#refresh",
79
+ class: INPUT_CLASSES,
80
+ value: value.presence,
81
+ name:,
82
+ maxlength: max_length,
83
+ disabled: (true if disabled),
84
+ autofocus: (true if autofocus),
85
+ pattern:,
86
+ # The three that make a phone offer the code from a message, and stop
87
+ # a keyboard offering everything else.
88
+ autocomplete: "one-time-code",
89
+ inputmode: "numeric",
90
+ spellcheck: "false"
91
+ }.compact.merge(input_attributes))
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module InputOtp
5
+ class Preview < ApplicationViewComponentPreview
6
+ def default
7
+ render_with_template
8
+ end
9
+
10
+ # Upstream's "Pattern", "Separator" and "Disabled" — and the FormBuilder,
11
+ # since a code that is not submitted is a code for nothing.
12
+ def variations
13
+ render_with_template
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ <%# Upstream's own example: six boxes, one real input over them.
2
+
3
+ Type into it and the boxes fill; the caret is drawn by the active box rather
4
+ than by the input, whose own is transparent. Paste a six-digit code and it
5
+ lands in one go, because the field the browser sees is an ordinary input.
6
+
7
+ Labelled, and that is the caller's job here as it is upstream's: there is one
8
+ input and it takes a name the way any input does. Unlabelled it fails axe,
9
+ which is how this preview shipped for about an hour. %>
10
+ <div class="flex flex-col gap-2">
11
+ <%= render(Shadcn::Label::Component.new(for: "otp-code")) { "Verification code" } %>
12
+ <%= render(Shadcn::InputOtp::Component.new(max_length: 6, name: "code", input: { id: "otp-code" })) do %>
13
+ <%= render(Shadcn::InputOtp::Group::Component.new) do %>
14
+ <% 6.times do %>
15
+ <%= render(Shadcn::InputOtp::Slot::Component.new) %>
16
+ <% end %>
17
+ <% end %>
18
+ <% end %>
19
+ </div>
@@ -0,0 +1,33 @@
1
+ <%# Upstream's "Separator", "Pattern" and "Disabled".
2
+
3
+ The separator is markup rather than an argument — two groups with a minus
4
+ between them — which is why the component takes no "grouping" option. The
5
+ pattern and the input mode are the input's own attributes, passed through
6
+ `input:`: this port has one input and hands the caller its keys rather than
7
+ inventing arguments for each. %>
8
+ <div class="flex flex-col gap-8">
9
+ <%= render(Shadcn::InputOtp::Component.new(max_length: 6, name: "grouped", input: { "aria-label": "Grouped code" })) do %>
10
+ <%= render(Shadcn::InputOtp::Group::Component.new) do %>
11
+ <% 3.times do %><%= render(Shadcn::InputOtp::Slot::Component.new) %><% end %>
12
+ <% end %>
13
+ <%= render(Shadcn::InputOtp::Separator::Component.new) %>
14
+ <%= render(Shadcn::InputOtp::Group::Component.new) do %>
15
+ <% 3.times do %><%= render(Shadcn::InputOtp::Slot::Component.new) %><% end %>
16
+ <% end %>
17
+ <% end %>
18
+
19
+ <%= render(Shadcn::InputOtp::Component.new(
20
+ max_length: 4, name: "letters",
21
+ input: { inputmode: "text", pattern: "[A-Za-z]*", "aria-label": "Letters only" }
22
+ )) do %>
23
+ <%= render(Shadcn::InputOtp::Group::Component.new) do %>
24
+ <% 4.times do %><%= render(Shadcn::InputOtp::Slot::Component.new) %><% end %>
25
+ <% end %>
26
+ <% end %>
27
+
28
+ <%= render(Shadcn::InputOtp::Component.new(max_length: 4, value: "1234", disabled: true, input: { "aria-label": "Disabled code" })) do %>
29
+ <%= render(Shadcn::InputOtp::Group::Component.new) do %>
30
+ <% 4.times do %><%= render(Shadcn::InputOtp::Slot::Component.new) %><% end %>
31
+ <% end %>
32
+ <% end %>
33
+ </div>
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module InputOtp
5
+ module Separator
6
+ # InputOTPSeparator — a minus between two groups. Upstream gives it a role
7
+ # and no classes at all.
8
+ class Component < ApplicationViewComponent
9
+ slot_name :"input-otp-separator"
10
+
11
+ def element_attributes(**defaults)
12
+ super(**{ role: "separator" }.merge(defaults))
13
+ end
14
+
15
+ def call
16
+ render_element(body: render(Icon::Component.new("minus")))
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module InputOtp
5
+ module Slot
6
+ # InputOTPSlot — one box. It holds no state of its own: the character, the
7
+ # active mark and the caret are all written by the controller from the one
8
+ # input's value and selection.
9
+ #
10
+ # Upstream takes an `index`; this reads the boxes in the order they appear,
11
+ # which is the same answer without a number for a caller to get wrong.
12
+ class Component < ApplicationViewComponent
13
+ CARET_CLASSES = "pointer-events-none absolute inset-0 flex items-center justify-center"
14
+ BAR_CLASSES = "h-4 w-px animate-caret-blink bg-foreground duration-1000"
15
+
16
+ slot_name :"input-otp-slot"
17
+
18
+ style do
19
+ base {
20
+ "relative flex h-9 w-9 items-center justify-center border-y border-r border-input " \
21
+ "text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l " \
22
+ "last:rounded-r-md aria-invalid:border-destructive data-[active=true]:z-10 " \
23
+ "data-[active=true]:border-ring data-[active=true]:ring-[3px] " \
24
+ "data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:border-destructive " \
25
+ "data-[active=true]:aria-invalid:ring-destructive/20 dark:bg-input/30 " \
26
+ "dark:data-[active=true]:aria-invalid:ring-destructive/40"
27
+ }
28
+ end
29
+
30
+ def element_attributes(**defaults)
31
+ super(**{
32
+ "data-active" => "false",
33
+ "data-shadcn--input-otp-target" => "slot"
34
+ }.merge(defaults))
35
+ end
36
+
37
+ def call
38
+ render_element(body: safe_join([ character, caret ]))
39
+ end
40
+
41
+ private
42
+
43
+ def character
44
+ tag.span("data-shadcn--input-otp-target": "char")
45
+ end
46
+
47
+ # Rendered always and hidden until the box is both active and empty,
48
+ # which is upstream's `hasFakeCaret`. Server-rendered rather than
49
+ # created on demand: a component that adds elements from JavaScript is a
50
+ # component `turbo:morph` argues with.
51
+ def caret
52
+ tag.div(tag.div(class: BAR_CLASSES),
53
+ class: CARET_CLASSES,
54
+ hidden: true,
55
+ "data-shadcn--input-otp-target": "caret")
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # The parts of the InputOTP family that are just an element with a
5
+ # `data-slot` and a fixed set of classes.
6
+ module InputOtp
7
+ extend Parts
8
+
9
+ part :group, slot: "input-otp-group", classes: "flex items-center"
10
+ end
11
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Item
5
+ # Port of registry/new-york-v4/ui/item.tsx
6
+ #
7
+ # `group/item` is half a mechanism: `ItemMedia`'s
8
+ # `group-has-[[data-slot=item-description]]/item:…` classes reach back into
9
+ # this named group to shift the media when a description is present. Both
10
+ # halves have to stay exactly as upstream wrote them — parity checks that
11
+ # each class exists, not that they still refer to each other.
12
+ class Component < ApplicationViewComponent
13
+ slot_name :item
14
+
15
+ style do
16
+ base {
17
+ "group/item flex flex-wrap items-center rounded-md border border-transparent " \
18
+ "text-sm transition-colors duration-100 outline-none focus-visible:border-ring " \
19
+ "focus-visible:ring-[3px] focus-visible:ring-ring/50 [a]:transition-colors " \
20
+ "[a]:hover:bg-accent/50"
21
+ }
22
+
23
+ variants {
24
+ variant {
25
+ default { "bg-transparent" }
26
+ outline { "border-border" }
27
+ muted { "bg-muted/50" }
28
+ }
29
+
30
+ size {
31
+ default { "gap-4 p-4" }
32
+ sm { "gap-2.5 px-4 py-3" }
33
+ }
34
+ }
35
+
36
+ defaults { { variant: :default, size: :default } }
37
+ end
38
+
39
+ attr_reader :variant, :size
40
+
41
+ def initialize(variant: :default, size: :default, **attributes)
42
+ @variant = variant&.to_sym || :default
43
+ @size = size&.to_sym || :default
44
+ super(**attributes)
45
+ end
46
+
47
+ def style_variants
48
+ { variant:, size: }
49
+ end
50
+
51
+ def element_attributes(**defaults)
52
+ super(**{ "data-variant" => variant, "data-size" => size }.merge(defaults))
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Item
5
+ module Group
6
+ # ItemGroup. Only a `data-slot` and classes apart from `role="list"`,
7
+ # which is why it is not a `part` — the macro declares no attributes.
8
+ #
9
+ # **`role="list"` obliges its children to be `role="listitem"`, and `Item`
10
+ # carries no role.** Adding one to `Item` itself would deviate from
11
+ # upstream's markup, so it is added here instead, through the `items`
12
+ # slot — the same layer the FormBuilder uses to supply the accessible
13
+ # name that Select and Switch cannot give themselves. A bare
14
+ # `Item::Component`, rendered outside this slot, still carries no role,
15
+ # exactly as upstream has it.
16
+ #
17
+ # `ItemSeparator` goes through the same slot rather than the block:
18
+ # `items` renders before block content (see `Card::Component`), so a
19
+ # separator placed as plain block content between two `with_item` calls
20
+ # would land after both of them instead of between them — the trap
21
+ # CLAUDE.md warns about. A polymorphic slot keeps both in the one
22
+ # ordered collection, where call order is preserved, and gives the
23
+ # separator its own setter (`with_separator`) rather than a flag that
24
+ # would otherwise let `Item` attributes reach a `role="none"` element.
25
+ class Component < ApplicationViewComponent
26
+ renders_many :items, types: {
27
+ item: {
28
+ renders: ->(**attributes) { Shadcn::Item::Component.new(role: "listitem", **attributes) },
29
+ as: :item
30
+ },
31
+ separator: {
32
+ renders: ->(**attributes) { Shadcn::Item::Separator::Component.new(**attributes) },
33
+ as: :separator
34
+ }
35
+ }
36
+
37
+ slot_name :"item-group"
38
+
39
+ style do
40
+ base { "group/item-group flex flex-col" }
41
+ end
42
+
43
+ def element_attributes(**defaults)
44
+ super(**{ role: "list" }.merge(defaults))
45
+ end
46
+
47
+ def call
48
+ render_element(body: safe_join([ *items, content ].compact))
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Item
5
+ module Media
6
+ # ItemMedia. Its base classes reach into the named group `Item` opens
7
+ # (`group-has-[[data-slot=item-description]]/item:…`) to shift the media
8
+ # down when the item has a description. That only works while `Item`
9
+ # still carries `group/item`.
10
+ class Component < ApplicationViewComponent
11
+ slot_name :"item-media"
12
+
13
+ style do
14
+ base {
15
+ "flex shrink-0 items-center justify-center gap-2 " \
16
+ "group-has-[[data-slot=item-description]]/item:translate-y-0.5 " \
17
+ "group-has-[[data-slot=item-description]]/item:self-start " \
18
+ "[&_svg]:pointer-events-none"
19
+ }
20
+
21
+ variants {
22
+ variant {
23
+ default { "bg-transparent" }
24
+ icon {
25
+ "size-8 rounded-sm border bg-muted [&_svg:not([class*='size-'])]:size-4"
26
+ }
27
+ image {
28
+ "size-10 overflow-hidden rounded-sm [&_img]:size-full [&_img]:object-cover"
29
+ }
30
+ }
31
+ }
32
+
33
+ defaults { { variant: :default } }
34
+ end
35
+
36
+ attr_reader :variant
37
+
38
+ def initialize(variant: :default, **attributes)
39
+ @variant = variant&.to_sym || :default
40
+ super(**attributes)
41
+ end
42
+
43
+ def style_variants
44
+ { variant: }
45
+ end
46
+
47
+ def element_attributes(**defaults)
48
+ super(**{ "data-variant" => variant }.merge(defaults))
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Item
5
+ class Preview < ApplicationViewComponentPreview
6
+ def default
7
+ render_with_template
8
+ end
9
+
10
+ def outline
11
+ render_with_template
12
+ end
13
+
14
+ # Upstream's "Link", "Avatar" and "Image".
15
+ def link
16
+ render_with_template
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ <%= render(Shadcn::Item::Group::Component.new(class: "w-full max-w-md")) do |group| %>
2
+ <% group.with_item do %>
3
+ <%= render(Shadcn::Item::Media::Component.new(variant: :icon)) do %>
4
+ <%= render(Shadcn::Icon::Component.new("circle")) %>
5
+ <% end %>
6
+ <%= render(Shadcn::Item::Content::Component.new) do %>
7
+ <%= render(Shadcn::Item::Title::Component.new) { "Basic plan" } %>
8
+ <%= render(Shadcn::Item::Description::Component.new) do %>
9
+ Everything you need to get started, with <a href="#">no card required</a>.
10
+ <% end %>
11
+ <% end %>
12
+ <%= render(Shadcn::Item::Actions::Component.new) do %>
13
+ <%= render(Shadcn::Button::Component.new(variant: :outline, size: :sm)) { "Choose" } %>
14
+ <% end %>
15
+ <% end %>
16
+
17
+ <% group.with_separator %>
18
+
19
+ <% group.with_item(variant: :muted, size: :sm) do %>
20
+ <%= render(Shadcn::Item::Header::Component.new) do %>
21
+ <%= render(Shadcn::Item::Title::Component.new) { "Pro plan" } %>
22
+ <%= render(Shadcn::Badge::Component.new) { "Popular" } %>
23
+ <% end %>
24
+ <%= render(Shadcn::Item::Content::Component.new) do %>
25
+ <%= render(Shadcn::Item::Description::Component.new) { "For teams shipping every day." } %>
26
+ <% end %>
27
+ <%= render(Shadcn::Item::Footer::Component.new) do %>
28
+ <%= render(Shadcn::Item::Description::Component.new) { "$20 per seat" } %>
29
+ <%= render(Shadcn::Button::Component.new(size: :sm)) { "Upgrade" } %>
30
+ <% end %>
31
+ <% end %>
32
+ <% end %>
@@ -0,0 +1,39 @@
1
+ <%# Upstream's "Link", "Avatar" and "Image".
2
+
3
+ An item that is a link is the whole element, not a link inside it: `as: :a`
4
+ is this port's `asChild`, so the row keeps its slots and its variants and
5
+ becomes an anchor. The media variants are the other half — `icon` is in the
6
+ default preview, `image` and the plain `default` are not.
7
+
8
+ Not inside an `ItemGroup`, and that is the point rather than an omission.
9
+ The group is `role="list"`, which may only hold `listitem` children, and
10
+ neither upstream's `Item` nor this one sets that role — so an item that
11
+ becomes an anchor becomes a `link` inside a `list`, which axe fails and a
12
+ screen reader reads as a broken list. A row that is a link belongs outside
13
+ the group, which is how upstream's own example shows it. %>
14
+ <div class="flex w-full max-w-md flex-col gap-4">
15
+ <%= render(Shadcn::Item::Component.new(as: :a, href: "#", variant: :outline)) do %>
16
+ <%= render(Shadcn::Item::Media::Component.new) do %>
17
+ <%= render(Shadcn::Avatar::Component.new) do |a| %>
18
+ <% a.with_fallback { "EW" } %>
19
+ <% end %>
20
+ <% end %>
21
+ <%= render(Shadcn::Item::Content::Component.new) do %>
22
+ <%= render(Shadcn::Item::Title::Component.new) { "Evelyn Wood" } %>
23
+ <%= render(Shadcn::Item::Description::Component.new) { "The whole row is the link." } %>
24
+ <% end %>
25
+ <%= render(Shadcn::Item::Actions::Component.new) do %>
26
+ <%= render(Shadcn::Icon::Component.new("chevron-right")) %>
27
+ <% end %>
28
+ <% end %>
29
+
30
+ <%= render(Shadcn::Item::Component.new(variant: :outline)) do %>
31
+ <%= render(Shadcn::Item::Media::Component.new(variant: :image)) do %>
32
+ <div class="flex size-full items-center justify-center bg-muted text-xs text-foreground/70">IMG</div>
33
+ <% end %>
34
+ <%= render(Shadcn::Item::Content::Component.new) do %>
35
+ <%= render(Shadcn::Item::Title::Component.new) { "A picture, cropped to the box" } %>
36
+ <%= render(Shadcn::Item::Description::Component.new) { "The image media is size-10, rounded and object-cover." } %>
37
+ <% end %>
38
+ <% end %>
39
+ </div>