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,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Combobox
5
+ # Port of registry/new-york-v4/ui/combobox.tsx — the one file of the 61 that
6
+ # is written against **Base UI** rather than Radix.
7
+ #
8
+ # That is the whole story of this port, and it is visible in the class
9
+ # strings: `data-open:`, `data-closed:`, `--anchor-width`,
10
+ # `--available-height`. Every other family here emits `data-state="open"`
11
+ # and reads `--radix-*`. Reproducing upstream's classes therefore means
12
+ # emitting Base UI's conventions in this family and nowhere else — which is
13
+ # what this does, and what `popper.js` publishes the four unprefixed
14
+ # variables for. See
15
+ # [features/combobox.md](../../../../.claude/docs/features/combobox.md).
16
+ #
17
+ # The root itself renders nothing upstream (`const Combobox =
18
+ # ComboboxPrimitive.Root`, a context provider), so it is a `display:
19
+ # contents` wrapper here — the same shape every context-only root in this
20
+ # gem takes.
21
+ class Component < ApplicationViewComponent
22
+ default_tag :div
23
+ slot_name :combobox
24
+
25
+ renders_one :combobox_input, "Shadcn::Combobox::Input::Component"
26
+ renders_one :combobox_content, "Shadcn::Combobox::Content::Component"
27
+
28
+ # In multiple mode the chips box replaces the field — upstream renders
29
+ # `ComboboxChips` where the single-selection examples render
30
+ # `ComboboxInput`, and the field moves *inside* it as `ComboboxChipsInput`.
31
+ # A separate slot rather than a variant of `combobox_input`, because the
32
+ # two are different components with different parents.
33
+ renders_one :combobox_chips, "Shadcn::Combobox::Chips::Component"
34
+
35
+ attr_reader :name, :label, :multiple
36
+
37
+ # `name:` submits with the form, as every other control in this gem does
38
+ # through a hidden input; `value:` is what is chosen and `label:` what is
39
+ # shown for it.
40
+ #
41
+ # `multiple:` is Base UI's own prop — "Whether multiple items can be
42
+ # selected" — and, as there, `value:` then takes an array. It submits the
43
+ # way Rails expects a collection to: `name` gains `[]` and there is one
44
+ # hidden input per chosen value, so `params[:post][:tag_ids]` is an array
45
+ # with no parsing on the receiving end.
46
+ def initialize(name: nil, value: nil, label: nil, multiple: false, **attributes)
47
+ @name = name
48
+ @multiple = multiple
49
+ @values = Array.wrap(value).map(&:to_s).reject(&:blank?)
50
+ @label = label
51
+ super(**attributes)
52
+ end
53
+
54
+ # The single chosen value. In multiple mode there is no such thing, and
55
+ # the controller reads `values` instead.
56
+ def value
57
+ multiple ? nil : @values.first
58
+ end
59
+
60
+ def values
61
+ @values
62
+ end
63
+
64
+ def element_attributes(**defaults)
65
+ super(**{
66
+ style: merged_style("display:contents"),
67
+ "data-controller" => "shadcn--combobox",
68
+ "data-shadcn--combobox-value-value" => value,
69
+ "data-shadcn--combobox-multiple-value" => (multiple.presence && "true"),
70
+ "data-shadcn--combobox-values-value" => (values.to_json if multiple),
71
+ # The controller writes the hidden inputs as chips come and go, and
72
+ # needs a name to write them under. Kept off the DOM when there is no
73
+ # name, like the inputs themselves.
74
+ "data-shadcn--combobox-name-value" => (field_name if multiple && name.present?)
75
+ }.compact.merge(defaults))
76
+ end
77
+
78
+ def call
79
+ render_element(body: safe_join([ combobox_input, combobox_chips, combobox_content, *hidden_inputs ].compact))
80
+ end
81
+
82
+ private
83
+
84
+ # Rails reads `foo[]` as a collection and `foo` as a scalar, so the
85
+ # brackets are what make several hidden inputs arrive as an array rather
86
+ # than as the last one to be parsed. Added only when the caller has not
87
+ # written them already.
88
+ def field_name
89
+ return name if !multiple || name.to_s.end_with?("[]")
90
+
91
+ "#{name}[]"
92
+ end
93
+
94
+ def hidden_inputs
95
+ return [] if name.blank?
96
+ return [ hidden_input(value) ] unless multiple
97
+
98
+ # An empty one first, so clearing every chip still submits the
99
+ # parameter — otherwise the key vanishes from the params and Rails
100
+ # leaves the association untouched instead of emptying it. This is the
101
+ # same trick `collection_select ... include_hidden: true` plays.
102
+ [ hidden_input("", target: false), *values.map { |v| hidden_input(v) } ]
103
+ end
104
+
105
+ def hidden_input(field_value, target: true)
106
+ attrs = { type: "hidden", name: field_name, value: field_value, autocomplete: "off" }
107
+ attrs["data-shadcn--combobox-target"] = "input" if target
108
+
109
+ tag.input(**attrs)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Combobox
5
+ module Content
6
+ # The panel. Its class string is where Base UI's conventions are most
7
+ # visible — `data-open:`, `data-closed:`, `--anchor-width`,
8
+ # `--available-width`, `--transform-origin` — and reproducing it is why
9
+ # the controller asks `popper.js` to publish those four unprefixed names
10
+ # beside the `--radix-*` ones it already computes.
11
+ class Component < ApplicationViewComponent
12
+ default_tag :div
13
+ slot_name :"combobox-content"
14
+
15
+ style do
16
+ base {
17
+ # `isolate z-50` is upstream's *Positioner*, which this port has no
18
+ # element for — `floating.js` makes the wrapper — so they ride on
19
+ # the panel itself.
20
+ "isolate z-50 group/combobox-content relative max-h-96 w-(--anchor-width) max-w-(--available-width) " \
21
+ "min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden " \
22
+ "rounded-md bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 " \
23
+ "data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 " \
24
+ "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 " \
25
+ "data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 " \
26
+ "*:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 " \
27
+ "*:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 " \
28
+ "*:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 " \
29
+ "data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95"
30
+ }
31
+ end
32
+
33
+ attr_reader :side, :align, :side_offset, :align_offset
34
+
35
+ def initialize(side: :bottom, align: :start, side_offset: 6, align_offset: 0, **attributes)
36
+ @side = side&.to_sym || :bottom
37
+ @align = align&.to_sym || :start
38
+ @side_offset = side_offset
39
+ @align_offset = align_offset
40
+ super(**attributes)
41
+ end
42
+
43
+ def element_attributes(**defaults)
44
+ super(**{
45
+ role: "listbox",
46
+ hidden: true,
47
+ "data-closed" => "",
48
+ "data-side" => side,
49
+ "data-shadcn--combobox-target" => "content",
50
+ "data-shadcn--combobox-side-value" => side,
51
+ "data-shadcn--combobox-align-value" => align,
52
+ "data-shadcn--combobox-side-offset-value" => side_offset,
53
+ "data-shadcn--combobox-align-offset-value" => align_offset
54
+ }.merge(defaults))
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Combobox
5
+ module Empty
6
+ # Shown when the filter leaves nothing. Upstream shows it with CSS alone —
7
+ # `hidden … group-data-empty/combobox-content:flex` — so the controller
8
+ # only has to put `data-empty` on the content, and this element needs no
9
+ # state of its own.
10
+ class Component < ApplicationViewComponent
11
+ default_tag :div
12
+ slot_name :"combobox-empty"
13
+
14
+ style do
15
+ base {
16
+ "hidden w-full justify-center py-2 text-center text-sm text-muted-foreground " \
17
+ "group-data-empty/combobox-content:flex"
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Combobox
5
+ module Input
6
+ # The field, inside an InputGroup with the chevron and — when asked for —
7
+ # the clear button in its trailing addon. Upstream composes exactly these
8
+ # three, and all three are ported here already.
9
+ class Component < ApplicationViewComponent
10
+ # No `data-slot` of its own: upstream renders
11
+ # `<ComboboxPrimitive.Input render={<InputGroupInput/>}/>`, so the field
12
+ # a person types into carries `input-group-control`. Read from the
13
+ # rendered example — `combobox-input` was invented here and does not
14
+ # exist upstream.
15
+ default_tag :input
16
+ slot_name :"input-group-control"
17
+
18
+ attr_reader :show_trigger, :show_clear, :disabled, :placeholder, :value
19
+
20
+ def initialize(show_trigger: true, show_clear: false, disabled: false,
21
+ placeholder: nil, value: nil, **attributes)
22
+ @show_trigger = show_trigger
23
+ @show_clear = show_clear
24
+ @disabled = disabled
25
+ @placeholder = placeholder
26
+ @value = value
27
+ super(**attributes)
28
+ end
29
+
30
+ # Base UI's combobox input is a `role="combobox"` with the list under
31
+ # `aria-controls`, and the panel's open state on `aria-expanded` — the
32
+ # same contract Radix's select trigger has, which is why the controller
33
+ # can wire both the same way.
34
+ def element_attributes(**defaults)
35
+ super(**{
36
+ type: "text",
37
+ value:,
38
+ placeholder:,
39
+ disabled: disabled.presence,
40
+ role: "combobox",
41
+ autocomplete: "off",
42
+ autocapitalize: "none",
43
+ autocorrect: "off",
44
+ spellcheck: "false",
45
+ "aria-autocomplete" => "list",
46
+ "aria-haspopup" => "listbox",
47
+ "aria-expanded" => "false",
48
+ "data-shadcn--combobox-target" => "search",
49
+ # `click`, not `focus`: taking an option puts the caret back in the
50
+ # field, and on `focus` that reopened the panel the choice had just
51
+ # closed. A pointer opens it by clicking, a keyboard by pressing
52
+ # Down — which is what both do anyway.
53
+ "data-action" => "input->shadcn--combobox#search click->shadcn--combobox#open " \
54
+ "keydown->shadcn--combobox#keydown"
55
+ }.compact.merge(defaults))
56
+ end
57
+
58
+ def call
59
+ render(Shadcn::InputGroup::Component.new(class: "w-auto")) do
60
+ safe_join([ field, addon ].compact)
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ # The input itself is an `InputGroupInput`, which is upstream's `render`
67
+ # prop — a subclass here, the gem's mapping for `asChild`.
68
+ def field
69
+ render(Shadcn::InputGroup::Input::Component.new(**element_attributes))
70
+ end
71
+
72
+ def addon
73
+ return if !show_trigger && !show_clear
74
+
75
+ render(Shadcn::InputGroup::Addon::Component.new(align: :"inline-end")) do
76
+ safe_join([
77
+ (render(Trigger::Component.new(disabled:)) if show_trigger),
78
+ (render(Clear::Component.new(disabled:)) if show_clear)
79
+ ].compact)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Combobox
5
+ module Item
6
+ # One option, with the tick that marks the chosen one. `data-highlighted`
7
+ # is the virtual focus — Base UI and Radix agree on that name, which is
8
+ # why the select's arrangement carries over unchanged.
9
+ class Component < ApplicationViewComponent
10
+ INDICATOR_CLASSES = "pointer-events-none absolute right-2 flex size-4 items-center justify-center"
11
+
12
+ default_tag :div
13
+ slot_name :"combobox-item"
14
+
15
+ style do
16
+ base {
17
+ "relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm " \
18
+ "outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground " \
19
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none " \
20
+ "[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
21
+ }
22
+ end
23
+
24
+ attr_reader :value, :label, :selected, :disabled
25
+
26
+ def initialize(value: nil, label: nil, selected: false, disabled: false, **attributes)
27
+ @value = value
28
+ @label = label
29
+ @selected = selected
30
+ @disabled = disabled
31
+ super(**attributes)
32
+ end
33
+
34
+ def element_attributes(**defaults)
35
+ super(**{
36
+ id: item_id,
37
+ role: "option",
38
+ "aria-selected" => selected.to_s,
39
+ "data-disabled" => (disabled.presence && ""),
40
+ "data-value" => value,
41
+ "data-label" => label,
42
+ "data-shadcn--combobox-target" => "item",
43
+ "data-action" => "click->shadcn--combobox#choose pointermove->shadcn--combobox#hover"
44
+ }.compact.merge(defaults))
45
+ end
46
+
47
+ def call
48
+ render_element(body: safe_join([ content, indicator ]))
49
+ end
50
+
51
+ private
52
+
53
+ def indicator
54
+ tag.span(
55
+ render(Icon::Component.new("check", class: "pointer-events-none size-4 pointer-coarse:size-5")),
56
+ class: INDICATOR_CLASSES,
57
+ "data-slot": "combobox-item-indicator",
58
+ hidden: !selected,
59
+ "data-shadcn--combobox-target": "indicator"
60
+ )
61
+ end
62
+
63
+ def item_id
64
+ attributes[:id] || "shadcn-combobox-item-#{SecureRandom.hex(4)}"
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Combobox
5
+ module List
6
+ # The scrolling part of the panel. `data-empty` is Base UI's, and the
7
+ # empty state's own class reads it through
8
+ # `group-data-empty/combobox-content:flex` — so the attribute lands on the
9
+ # content and the padding rule here reads it too.
10
+ class Component < ApplicationViewComponent
11
+ default_tag :div
12
+ slot_name :"combobox-list"
13
+
14
+ style do
15
+ base {
16
+ "max-h-[min(calc(--spacing(96)---spacing(9)),calc(var(--available-height)---spacing(9)))] " \
17
+ "scroll-py-1 overflow-y-auto p-1 data-empty:p-0"
18
+ }
19
+ end
20
+
21
+ def element_attributes(**defaults)
22
+ super(**{ "data-shadcn--combobox-target" => "list" }.merge(defaults))
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Combobox
5
+ class Preview < ApplicationViewComponentPreview
6
+ # Upstream's own first example: type to filter, arrows to walk, Enter to
7
+ # take.
8
+ def default
9
+ render_with_template
10
+ end
11
+
12
+ # Upstream's "Clear": the X that empties the field, which hides the
13
+ # chevron while it is there — upstream's own rule, in a class.
14
+ def with_clear
15
+ render_with_template
16
+ end
17
+
18
+ # Groups with labels, and the separator between them.
19
+ def grouped
20
+ render_with_template
21
+ end
22
+
23
+ # Multiple selection: the chips box in place of the field. Two values
24
+ # start chosen, so the server-rendered half — the ticks, the chips and the
25
+ # hidden inputs agreeing before anything is clicked — is covered too.
26
+ def multiple
27
+ render_with_template
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ <%# Upstream's own example. The items are server-rendered and the controller
2
+ filters them by substring — Base UI's default, and the same call the
3
+ searchable select makes: a combobox completes a value from a list its caller
4
+ ordered, where the palette ranks answers. %>
5
+ <%
6
+ languages = [ %w[js JavaScript], %w[rb Ruby], %w[py Python], %w[go Go], %w[rs Rust], %w[ex Elixir] ]
7
+ %>
8
+ <div class="w-64">
9
+ <%= render(Shadcn::Combobox::Component.new(name: "project[language]")) do |combobox| %>
10
+ <% combobox.with_combobox_input(placeholder: "Search a language…") %>
11
+ <% combobox.with_combobox_content do %>
12
+ <%= render(Shadcn::Combobox::List::Component.new) do %>
13
+ <% languages.each do |value, label| %>
14
+ <%= render(Shadcn::Combobox::Item::Component.new(value:, label:)) { label } %>
15
+ <% end %>
16
+ <% end %>
17
+ <%= render(Shadcn::Combobox::Empty::Component.new) { "No language found." } %>
18
+ <% end %>
19
+ <% end %>
20
+ </div>
@@ -0,0 +1,25 @@
1
+ <%# Groups, their labels and the rule between them — the three parts `part`
2
+ declares, because each is an element with a `data-slot` and fixed classes. %>
3
+ <div class="w-64">
4
+ <%= render(Shadcn::Combobox::Component.new(name: "project[stack]")) do |combobox| %>
5
+ <% combobox.with_combobox_input(placeholder: "Search…") %>
6
+ <% combobox.with_combobox_content do %>
7
+ <%= render(Shadcn::Combobox::List::Component.new) do %>
8
+ <%= render(Shadcn::Combobox::Group::Component.new) do %>
9
+ <%= render(Shadcn::Combobox::Label::Component.new) { "Languages" } %>
10
+ <% [ %w[rb Ruby], %w[js JavaScript] ].each do |value, label| %>
11
+ <%= render(Shadcn::Combobox::Item::Component.new(value:, label:)) { label } %>
12
+ <% end %>
13
+ <% end %>
14
+ <%= render(Shadcn::Combobox::Separator::Component.new) %>
15
+ <%= render(Shadcn::Combobox::Group::Component.new) do %>
16
+ <%= render(Shadcn::Combobox::Label::Component.new) { "Databases" } %>
17
+ <% [ %w[pg PostgreSQL], %w[sqlite SQLite] ].each do |value, label| %>
18
+ <%= render(Shadcn::Combobox::Item::Component.new(value:, label:)) { label } %>
19
+ <% end %>
20
+ <% end %>
21
+ <% end %>
22
+ <%= render(Shadcn::Combobox::Empty::Component.new) { "Nothing found." } %>
23
+ <% end %>
24
+ <% end %>
25
+ </div>
@@ -0,0 +1,28 @@
1
+ <%# Multiple selection: the chips box in place of the field, one chip per chosen
2
+ value, and `multiple: true` on the root.
3
+
4
+ Two of the values start chosen, so the preview covers the server-rendered
5
+ side as well as the controller's — the ticks in the list, the hidden inputs
6
+ and the chips all have to agree before a single click happens. %>
7
+ <%
8
+ languages = [ %w[js JavaScript], %w[rb Ruby], %w[py Python], %w[go Go], %w[rs Rust], %w[ex Elixir] ]
9
+ chosen = %w[rb go]
10
+ %>
11
+ <div class="w-80">
12
+ <%= render(Shadcn::Combobox::Component.new(name: "project[languages]", value: chosen, multiple: true)) do |combobox| %>
13
+ <% combobox.with_combobox_chips do %>
14
+ <% chosen.each do |value| %>
15
+ <%= render(Shadcn::Combobox::Chip::Component.new(value:)) { languages.to_h[value] } %>
16
+ <% end %>
17
+ <%= render(Shadcn::Combobox::ChipInput::Component.new(placeholder: "Add a language…")) %>
18
+ <% end %>
19
+ <% combobox.with_combobox_content do %>
20
+ <%= render(Shadcn::Combobox::List::Component.new) do %>
21
+ <% languages.each do |value, label| %>
22
+ <%= render(Shadcn::Combobox::Item::Component.new(value:, label:, selected: chosen.include?(value))) { label } %>
23
+ <% end %>
24
+ <% end %>
25
+ <%= render(Shadcn::Combobox::Empty::Component.new) { "No language found." } %>
26
+ <% end %>
27
+ <% end %>
28
+ </div>
@@ -0,0 +1,16 @@
1
+ <%# Upstream's "Clear". The chevron hides itself while the X is there —
2
+ `group-has-data-[slot=combobox-clear]/input-group:hidden`, upstream's own
3
+ class, which works here because the port renders the same `data-slot`. %>
4
+ <div class="w-64">
5
+ <%= render(Shadcn::Combobox::Component.new(name: "project[framework]", value: "rails", label: "Rails")) do |combobox| %>
6
+ <% combobox.with_combobox_input(placeholder: "Search a framework…", value: "Rails", show_clear: true) %>
7
+ <% combobox.with_combobox_content do %>
8
+ <%= render(Shadcn::Combobox::List::Component.new) do %>
9
+ <% [ %w[rails Rails], %w[hotwire Hotwire], %w[sinatra Sinatra], %w[hanami Hanami] ].each do |value, label| %>
10
+ <%= render(Shadcn::Combobox::Item::Component.new(value:, label:, selected: value == "rails")) { label } %>
11
+ <% end %>
12
+ <% end %>
13
+ <%= render(Shadcn::Combobox::Empty::Component.new) { "Nothing found." } %>
14
+ <% end %>
15
+ <% end %>
16
+ </div>
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Combobox
5
+ module Trigger
6
+ # The chevron beside the field. Upstream renders it *through*
7
+ # `InputGroupButton` with `asChild`, so the button's own classes land on
8
+ # it — here that is a subclass, which is this gem's mapping for `asChild`.
9
+ class Component < Shadcn::InputGroup::Button::Component
10
+ ICON_CLASSES = "pointer-events-none size-4 text-muted-foreground"
11
+
12
+ # `input-group-button`, not `combobox-trigger`: upstream's wrapper sets
13
+ # the slot last and `asChild` merges the two into one element, so that
14
+ # is the name the rendered button carries. Confirmed on the page rather
15
+ # than reasoned about.
16
+ slot_name :"input-group-button"
17
+
18
+ def initialize(**attributes)
19
+ super(variant: :ghost, size: :"icon-xs", **attributes)
20
+ end
21
+
22
+ def element_attributes(**defaults)
23
+ super(**{
24
+ role: "combobox",
25
+ # Named, where upstream's is not. Its rendered trigger is a
26
+ # `role="combobox"` with a chevron in it and no text at all, and a
27
+ # role that takes a name and has none is what axe calls a
28
+ # `button-name` violation — the same correction Select, Checkbox and
29
+ # Switch needed here.
30
+ "aria-label" => shadcn_t("combobox.toggle"),
31
+ "aria-haspopup" => "dialog",
32
+ "aria-expanded" => "false",
33
+ tabindex: "0",
34
+ "data-shadcn--combobox-target" => "trigger",
35
+ "data-action" => "click->shadcn--combobox#toggle"
36
+ }.merge(defaults))
37
+ end
38
+
39
+ def css_classes(extra = nil)
40
+ super([
41
+ "[&_svg:not([class*='size-'])]:size-4",
42
+ "group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent",
43
+ extra
44
+ ].compact.join(" "))
45
+ end
46
+
47
+ def call
48
+ render_element(body: render(Icon::Component.new("chevron-down", class: ICON_CLASSES,
49
+ "data-slot": "combobox-trigger-icon")))
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # The parts of the Combobox family that are an element with a `data-slot` and
5
+ # fixed classes.
6
+ module Combobox
7
+ extend Parts
8
+
9
+ part :value, slot: "combobox-value", tag: :span
10
+ part :group, slot: "combobox-group"
11
+ part :label, slot: "combobox-label",
12
+ classes: "px-2 py-1.5 text-xs text-muted-foreground pointer-coarse:px-3 " \
13
+ "pointer-coarse:py-2 pointer-coarse:text-sm"
14
+ part :separator, slot: "combobox-separator", classes: "-mx-1 my-1 h-px bg-border"
15
+ end
16
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Command
5
+ # Port of registry/new-york-v4/ui/command.tsx, whose behaviour is `cmdk` —
6
+ # 1,091 lines of TSX plus a 162-line fuzzy scorer, over four Radix packages,
7
+ # vendored at `vendor/cmdk/`.
8
+ #
9
+ # Every class is in the TSX, and unlike `chart.tsx` its selectors reach a
10
+ # DOM this port renders: `[cmdk-item]`, `[cmdk-group-heading]`,
11
+ # `[cmdk-input-wrapper]` are how shadcn spaces the palette out, so the parts
12
+ # carry those bare attributes and upstream's own rules apply. See
13
+ # [features/command.md](../../../../.claude/docs/features/command.md).
14
+ class Component < ApplicationViewComponent
15
+ default_tag :div
16
+ slot_name :command
17
+
18
+ style do
19
+ base { "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground" }
20
+ end
21
+
22
+ renders_one :command_input, ->(**options) { Input::Component.new(ids:, **options) }
23
+ renders_one :command_list, ->(**options) { List::Component.new(ids:, **options) }
24
+
25
+ attr_reader :label
26
+
27
+ # `label:` names the listbox, as cmdk's own `label` prop does
28
+ # (`vendor/cmdk/index.tsx:350`): a list of options is a control, and a
29
+ # control with no name is one a screen reader reads as nothing.
30
+ def initialize(label: nil, **attributes)
31
+ @label = label
32
+ super(**attributes)
33
+ end
34
+
35
+ # The three ids the ARIA wiring needs, decided once here and handed to the
36
+ # parts: an input points `aria-controls` at the list, and the list is
37
+ # named by a label that may not exist yet when the input renders.
38
+ def ids
39
+ @ids ||= { input: generated(:input), list: generated(:list), label: generated(:label) }
40
+ end
41
+
42
+ def element_attributes(**defaults)
43
+ super(**{
44
+ "cmdk-root" => "",
45
+ "data-controller" => "shadcn--command"
46
+ }.merge(defaults))
47
+ end
48
+
49
+ def call
50
+ render_element(body: safe_join([ hidden_label, command_input, command_list, content ].compact))
51
+ end
52
+
53
+ private
54
+
55
+ # cmdk renders the label as a visually hidden element when it is given a
56
+ # string (index.tsx:591-596). Without it `aria-labelledby` on the input
57
+ # points at nothing, which is worse than no label at all.
58
+ def hidden_label
59
+ return unless label.present?
60
+
61
+ tag.label(label, id: ids[:label], for: ids[:input], class: "sr-only")
62
+ end
63
+
64
+ def generated(part) = "shadcn-command-#{part}-#{SecureRandom.hex(4)}"
65
+ end
66
+ end
67
+ end