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,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Command
5
+ module Dialog
6
+ # The palette: a Command inside a Dialog, which is what upstream's
7
+ # `CommandDialog` is — it renders no markup of its own beyond the classes
8
+ # it layers on both.
9
+ #
10
+ # The title and the description are `sr-only` and required rather than
11
+ # optional, exactly as upstream defaults them: a dialog with no
12
+ # accessible name is a dialog a screen reader opens into silence.
13
+ class Component < ApplicationViewComponent
14
+ # Upstream's own spacing for a palette in a dialog, which is where the
15
+ # `cmdk-*` attributes earn their keep: the rows are taller and the
16
+ # headings are indented, and every selector reaches this port's DOM.
17
+ COMMAND_CLASSES = "**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 " \
18
+ "[&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground " \
19
+ "[&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 " \
20
+ "[&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 " \
21
+ "[&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 " \
22
+ "[&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"
23
+ CONTENT_CLASSES = "overflow-hidden p-0"
24
+
25
+ attr_reader :title, :description, :show_close_button, :label
26
+
27
+ def initialize(title: "Command Palette", description: "Search for a command to run...",
28
+ show_close_button: true, label: nil, **attributes)
29
+ @title = title
30
+ @description = description
31
+ @show_close_button = show_close_button
32
+ @label = label
33
+ super(**attributes)
34
+ end
35
+
36
+ # The trigger belongs to the Dialog, not to this component, so the slot
37
+ # only *carries* what the caller wrote — its content, and the attributes
38
+ # that come with it — and `call` hands both to the Dialog's own trigger.
39
+ # Nesting one trigger inside another renders an empty button, which is
40
+ # what axe reported the first time.
41
+ # `view_context.capture`, and the block kept rather than rendered here:
42
+ # the block was written in the caller's template, so capturing it inside
43
+ # this component writes to the wrong buffer and the button comes out
44
+ # empty — which is what axe reported, twice.
45
+ renders_one :trigger, lambda { |**options, &block|
46
+ @trigger_options = options
47
+ @trigger_block = block
48
+ nil
49
+ }
50
+
51
+ def call
52
+ render(Shadcn::Dialog::Component.new(**attributes)) do |dialog|
53
+ dialog.with_trigger(**@trigger_options.to_h) { view_context.capture(&@trigger_block) } if trigger?
54
+ dialog.with_dialog_content(class: CONTENT_CLASSES, show_close_button:) do |dialog_content|
55
+ dialog_content.with_header(class: "sr-only") do |header|
56
+ header.with_title { title }
57
+ header.with_description { description }
58
+ end
59
+ concat(command)
60
+ end
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def command
67
+ render(Command::Component.new(label:, class: COMMAND_CLASSES)) { content }
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Command
5
+ module Empty
6
+ # What is shown when nothing matches. Rendered hidden and revealed by the
7
+ # controller: the server cannot know what a caller will type, and an empty
8
+ # state that only exists once JavaScript has run is one a reader sees
9
+ # flash in.
10
+ class Component < ApplicationViewComponent
11
+ default_tag :div
12
+ slot_name :"command-empty"
13
+
14
+ style do
15
+ base { "py-6 text-center text-sm" }
16
+ end
17
+
18
+ def element_attributes(**defaults)
19
+ super(**{
20
+ "cmdk-empty" => "",
21
+ role: "presentation",
22
+ hidden: true,
23
+ "data-shadcn--command-target" => "empty"
24
+ }.merge(defaults))
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Command
5
+ module Group
6
+ # A titled run of items. cmdk renders three elements — the group, an
7
+ # `aria-hidden` heading and a `role="group"` wrapper named by it
8
+ # (`vendor/cmdk/index.tsx:750-764`) — and shadcn's classes select the
9
+ # middle one by `[cmdk-group-heading]`, so all three are reproduced.
10
+ #
11
+ # The whole group hides when every item in it is filtered away, which is
12
+ # the controller's job and the reason `hidden` is not a caller's argument.
13
+ class Component < ApplicationViewComponent
14
+ default_tag :div
15
+ slot_name :"command-group"
16
+
17
+ style do
18
+ base {
19
+ "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 " \
20
+ "[&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs " \
21
+ "[&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground"
22
+ }
23
+ end
24
+
25
+ attr_reader :heading
26
+
27
+ def initialize(heading: nil, **attributes)
28
+ @heading = heading
29
+ super(**attributes)
30
+ end
31
+
32
+ def element_attributes(**defaults)
33
+ super(**{
34
+ "cmdk-group" => "",
35
+ role: "presentation",
36
+ "data-shadcn--command-target" => "group"
37
+ }.merge(defaults))
38
+ end
39
+
40
+ def call
41
+ render_element(body: safe_join([ title, items ].compact))
42
+ end
43
+
44
+ private
45
+
46
+ def heading_id
47
+ @heading_id ||= "shadcn-command-heading-#{SecureRandom.hex(4)}"
48
+ end
49
+
50
+ def title
51
+ return unless heading.present?
52
+
53
+ tag.div(heading, id: heading_id, "cmdk-group-heading": "", "aria-hidden": "true")
54
+ end
55
+
56
+ def items
57
+ tag.div(content, "cmdk-group-items": "", role: "group",
58
+ "aria-labelledby": (heading_id if heading.present?))
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Command
5
+ module Input
6
+ # The search field, wrapped in the row that holds the magnifier —
7
+ # upstream renders both from one component and gives each its own slot.
8
+ class Component < ApplicationViewComponent
9
+ WRAPPER_CLASSES = "flex h-9 items-center gap-2 border-b px-3"
10
+ ICON_CLASSES = "size-4 shrink-0 opacity-50"
11
+
12
+ default_tag :input
13
+ slot_name :"command-input"
14
+
15
+ style do
16
+ base {
17
+ "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden " \
18
+ "placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
19
+ }
20
+ end
21
+
22
+ attr_reader :ids
23
+
24
+ def initialize(ids: {}, **attributes)
25
+ @ids = ids
26
+ super(**attributes)
27
+ end
28
+
29
+ # cmdk's own attributes, read from `vendor/cmdk/index.tsx:801-816`. The
30
+ # combobox is always expanded because the list is always there — a
31
+ # palette has nothing to open.
32
+ def element_attributes(**defaults)
33
+ super(**{
34
+ "cmdk-input" => "",
35
+ type: "text",
36
+ id: ids[:input],
37
+ role: "combobox",
38
+ autocomplete: "off",
39
+ autocorrect: "off",
40
+ spellcheck: "false",
41
+ "aria-autocomplete" => "list",
42
+ "aria-expanded" => "true",
43
+ "aria-controls" => ids[:list],
44
+ "aria-labelledby" => ids[:label],
45
+ "data-shadcn--command-target" => "search",
46
+ "data-action" => "input->shadcn--command#search keydown->shadcn--command#keydown"
47
+ }.compact.merge(defaults))
48
+ end
49
+
50
+ # The wrapper is the component's own markup rather than a part a caller
51
+ # composes, which is upstream's shape too.
52
+ def call
53
+ tag.div(
54
+ safe_join([
55
+ render(Icon::Component.new("search", class: ICON_CLASSES)),
56
+ render_element
57
+ ]),
58
+ class: WRAPPER_CLASSES,
59
+ "data-slot": "command-input-wrapper",
60
+ "cmdk-input-wrapper": ""
61
+ )
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Command
5
+ module Item
6
+ # One option. `role="option"`, `aria-selected` for the virtual focus, and
7
+ # `data-selected` for the classes — cmdk sets both, and shadcn's own
8
+ # `data-[selected=true]:bg-accent` reads the second.
9
+ class Component < ApplicationViewComponent
10
+ default_tag :div
11
+ slot_name :"command-item"
12
+
13
+ style do
14
+ base {
15
+ "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm " \
16
+ "outline-hidden select-none data-[disabled=true]:pointer-events-none " \
17
+ "data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent " \
18
+ "data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 " \
19
+ "[&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground"
20
+ }
21
+ end
22
+
23
+ attr_reader :value, :keywords, :disabled, :selected
24
+
25
+ # `value:` is what is searched where it is given, and the item's own text
26
+ # otherwise — cmdk's rule. `keywords:` are searched too and never shown,
27
+ # which is how "Preferences" is found by typing "settings".
28
+ def initialize(value: nil, keywords: nil, disabled: false, selected: false, **attributes)
29
+ @value = value
30
+ @keywords = Array(keywords)
31
+ @disabled = disabled
32
+ @selected = selected
33
+ super(**attributes)
34
+ end
35
+
36
+ def element_attributes(**defaults)
37
+ super(**{
38
+ "cmdk-item" => "",
39
+ id: item_id,
40
+ role: "option",
41
+ "aria-disabled" => (disabled.presence && "true"),
42
+ "aria-selected" => selected.to_s,
43
+ "data-disabled" => disabled.to_s,
44
+ "data-selected" => selected.to_s,
45
+ "data-value" => value,
46
+ "data-keywords" => (keywords.join(" ").presence),
47
+ "data-shadcn--command-target" => "item",
48
+ "data-action" => "click->shadcn--command#choose pointermove->shadcn--command#hover"
49
+ }.compact.merge(defaults))
50
+ end
51
+
52
+ private
53
+
54
+ # An item needs a name of its own for `aria-activedescendant` to point
55
+ # at it, and a caller has no reason to invent one.
56
+ def item_id
57
+ attributes[:id] || "shadcn-command-item-#{SecureRandom.hex(4)}"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Command
5
+ module List
6
+ # The scrolling list of options. `role="listbox"`, and the input drives it
7
+ # from outside through `aria-activedescendant` — the virtual focus this
8
+ # gem already uses for the searchable select.
9
+ class Component < ApplicationViewComponent
10
+ default_tag :div
11
+ slot_name :"command-list"
12
+
13
+ style do
14
+ base { "max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto" }
15
+ end
16
+
17
+ attr_reader :ids
18
+
19
+ def initialize(ids: {}, **attributes)
20
+ @ids = ids
21
+ super(**attributes)
22
+ end
23
+
24
+ def element_attributes(**defaults)
25
+ super(**{
26
+ "cmdk-list" => "",
27
+ id: ids[:list],
28
+ role: "listbox",
29
+ "aria-labelledby" => ids[:label],
30
+ "data-shadcn--command-target" => "list"
31
+ }.compact.merge(defaults))
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Command
5
+ class Preview < ApplicationViewComponentPreview
6
+ # Upstream's own example: a palette in a box, with two groups and the
7
+ # shortcuts beside the rows.
8
+ def default
9
+ render_with_template
10
+ end
11
+
12
+ # Upstream's "Dialog": the same list inside a Dialog. The shortcut that
13
+ # opens it is the caller's — `data-action` on any element, or the Dialog's
14
+ # own trigger.
15
+ def dialog
16
+ render_with_template
17
+ end
18
+
19
+ # What ranking buys, which filtering does not: type "gp" and Group Policy
20
+ # comes first, because a palette answers "what did you mean".
21
+ def ranking
22
+ render_with_template
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ <%# Upstream's own example. The items are ordinary components in the block, in
2
+ order — the list is server-rendered and the controller only filters, ranks
3
+ and walks it. %>
4
+ <%= render(Shadcn::Command::Component.new(label: "Command palette", class: "rounded-lg border shadow-md md:min-w-[450px]")) do |command| %>
5
+ <% command.with_command_input(placeholder: "Type a command or search...") %>
6
+ <% command.with_command_list do %>
7
+ <%= render(Shadcn::Command::Empty::Component.new) { "No results found." } %>
8
+ <%= render(Shadcn::Command::Group::Component.new(heading: "Suggestions")) do %>
9
+ <%= render(Shadcn::Command::Item::Component.new) do %>
10
+ <%= render(Shadcn::Icon::Component.new("calendar")) %>
11
+ <span>Calendar</span>
12
+ <% end %>
13
+ <%= render(Shadcn::Command::Item::Component.new) do %>
14
+ <%= render(Shadcn::Icon::Component.new("search")) %>
15
+ <span>Search Emoji</span>
16
+ <% end %>
17
+ <%= render(Shadcn::Command::Item::Component.new(disabled: true)) do %>
18
+ <%= render(Shadcn::Icon::Component.new("calculator")) %>
19
+ <span>Calculator</span>
20
+ <% end %>
21
+ <% end %>
22
+ <%= render(Shadcn::Command::Separator::Component.new) %>
23
+ <%= render(Shadcn::Command::Group::Component.new(heading: "Settings")) do %>
24
+ <%= render(Shadcn::Command::Item::Component.new(keywords: %w[account me])) do %>
25
+ <%= render(Shadcn::Icon::Component.new("user")) %>
26
+ <span>Profile</span>
27
+ <%= render(Shadcn::Command::Shortcut::Component.new) { "⌘P" } %>
28
+ <% end %>
29
+ <%= render(Shadcn::Command::Item::Component.new(keywords: %w[invoice payment])) do %>
30
+ <%= render(Shadcn::Icon::Component.new("credit-card")) %>
31
+ <span>Billing</span>
32
+ <%= render(Shadcn::Command::Shortcut::Component.new) { "⌘B" } %>
33
+ <% end %>
34
+ <%= render(Shadcn::Command::Item::Component.new(keywords: %w[preferences])) do %>
35
+ <%= render(Shadcn::Icon::Component.new("settings")) %>
36
+ <span>Settings</span>
37
+ <%= render(Shadcn::Command::Shortcut::Component.new) { "⌘S" } %>
38
+ <% end %>
39
+ <% end %>
40
+ <% end %>
41
+ <% end %>
@@ -0,0 +1,33 @@
1
+ <%# Upstream's "Dialog": the same palette inside a Dialog, whose title and
2
+ description are `sr-only` and not optional — a dialog with no accessible
3
+ name opens into silence.
4
+
5
+ The shortcut that opens it is the caller's: any element with the Dialog's
6
+ own trigger action, or a `keydown@document` of your own. %>
7
+ <%= render(Shadcn::Command::Dialog::Component.new(label: "Command palette")) do |palette| %>
8
+ <% palette.with_trigger(class: Shadcn::Button::Component.variant_classes(variant: :outline)) do %>
9
+ Open the palette
10
+ <% end %>
11
+ <%= render(Shadcn::Command::Input::Component.new(placeholder: "Type a command or search...")) %>
12
+ <%= render(Shadcn::Command::List::Component.new) do %>
13
+ <%= render(Shadcn::Command::Empty::Component.new) { "No results found." } %>
14
+ <%= render(Shadcn::Command::Group::Component.new(heading: "Suggestions")) do %>
15
+ <% [ [ "calendar", "Calendar" ], [ "search", "Search Emoji" ], [ "calculator", "Calculator" ] ].each do |icon, label| %>
16
+ <%= render(Shadcn::Command::Item::Component.new) do %>
17
+ <%= render(Shadcn::Icon::Component.new(icon)) %>
18
+ <span><%= label %></span>
19
+ <% end %>
20
+ <% end %>
21
+ <% end %>
22
+ <%= render(Shadcn::Command::Separator::Component.new) %>
23
+ <%= render(Shadcn::Command::Group::Component.new(heading: "Settings")) do %>
24
+ <% [ [ "user", "Profile", "⌘P" ], [ "credit-card", "Billing", "⌘B" ], [ "settings", "Settings", "⌘S" ] ].each do |icon, label, key| %>
25
+ <%= render(Shadcn::Command::Item::Component.new) do %>
26
+ <%= render(Shadcn::Icon::Component.new(icon)) %>
27
+ <span><%= label %></span>
28
+ <%= render(Shadcn::Command::Shortcut::Component.new) { key } %>
29
+ <% end %>
30
+ <% end %>
31
+ <% end %>
32
+ <% end %>
33
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <%# What ranking buys, and filtering does not. Type `gp`: Group Policy comes
2
+ first, because `cmdk`'s scorer rewards a match at the start of a word — the
3
+ same file is ported at `app/javascript/shadcn/command_score.js`.
4
+
5
+ Then type `settings` and Preferences appears, which has the word nowhere in
6
+ it: `keywords:` are searched and never shown. %>
7
+ <%= render(Shadcn::Command::Component.new(label: "Search", class: "rounded-lg border shadow-md md:min-w-[450px]")) do |command| %>
8
+ <% command.with_command_input(placeholder: "Try gp, or settings…") %>
9
+ <% command.with_command_list do %>
10
+ <%= render(Shadcn::Command::Empty::Component.new) { "No results found." } %>
11
+ <% { "Groups" => nil, "Group Policy" => nil, "Grouping rules" => nil,
12
+ "Preferences" => %w[settings config], "General" => nil }.each do |label, keywords| %>
13
+ <%= render(Shadcn::Command::Item::Component.new(keywords:)) { label } %>
14
+ <% end %>
15
+ <% end %>
16
+ <% end %>
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Command
5
+ module Separator
6
+ # A rule between groups, and decorative: `aria-hidden` where cmdk writes
7
+ # `role="separator"` (`vendor/cmdk/index.tsx:780`).
8
+ #
9
+ # A `role="listbox"` may only contain options and groups, and upstream's
10
+ # own palette puts the separator inside the list — so cmdk's markup makes
11
+ # the listbox invalid, and axe fails it. Nothing is lost by hiding it: a
12
+ # separator with no name conveys nothing to a screen reader, and the
13
+ # groups either side of it are already named. This is the same call the
14
+ # toaster made about a `role` on an `<li>`.
15
+ class Component < ApplicationViewComponent
16
+ default_tag :div
17
+ slot_name :"command-separator"
18
+
19
+ style do
20
+ base { "-mx-1 h-px bg-border" }
21
+ end
22
+
23
+ def element_attributes(**defaults)
24
+ super(**{
25
+ "cmdk-separator" => "",
26
+ "aria-hidden" => "true",
27
+ "data-shadcn--command-target" => "separator"
28
+ }.merge(defaults))
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # The parts of the Command family that are an element with a `data-slot` and
5
+ # fixed classes. Everything else — the root, the input, the list, the group,
6
+ # the item, the separator and the dialog — carries an attribute `part` cannot
7
+ # express, mostly `cmdk-*` and a role.
8
+ module Command
9
+ extend Parts
10
+
11
+ part :shortcut, slot: "command-shortcut", tag: :span,
12
+ classes: "ml-auto text-xs tracking-widest text-muted-foreground"
13
+ end
14
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Concerns
5
+ # Shared behaviour of the three components shadcn builds on Radix's Dialog
6
+ # primitive: Dialog, Sheet and AlertDialog.
7
+ #
8
+ # It is a module rather than a superclass because ViewComponent refuses to
9
+ # let a subclass redeclare an inherited slot, and each of the three needs
10
+ # its own `trigger` / content slot types.
11
+ module ModalRoot
12
+ def self.included(base)
13
+ base.class_eval do
14
+ attr_reader :open, :modal
15
+ end
16
+ end
17
+
18
+ # Radix keeps alert dialogs open on an outside click; AlertDialog says so
19
+ # by overriding this.
20
+ def dismissable? = true
21
+
22
+ def initialize(open: false, modal: true, **attributes)
23
+ @open = open
24
+ @modal = modal
25
+ super(**attributes)
26
+ end
27
+
28
+ def element_attributes(**defaults)
29
+ super(**{
30
+ style: merged_style(ApplicationViewComponent::CONTENTS_STYLE),
31
+ "data-controller" => "shadcn--dialog",
32
+ "data-shadcn--dialog-open-value" => open,
33
+ "data-shadcn--dialog-modal-value" => modal,
34
+ "data-shadcn--dialog-dismissable-value" => dismissable?
35
+ }.merge(defaults))
36
+ end
37
+
38
+ def call
39
+ render_element(body: safe_join([ trigger, dialog_content, content ].compact))
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Concerns
5
+ # The hidden input that carries a control's value into the form, for the
6
+ # three families whose value is a single scalar: Select, RadioGroup and
7
+ # ToggleGroup.
8
+ #
9
+ # That input is **ours** — Radix bubbles a native control instead, and
10
+ # nothing upstream describes it. Which is precisely why it needed an opening:
11
+ # a host cannot address something the library invented and keeps to itself.
12
+ #
13
+ # `input_attributes:` is that opening. It reaches the input and nothing else:
14
+ #
15
+ # Shadcn::Select::Component.new(
16
+ # name: "post[author_id]",
17
+ # input_attributes: { id: "author-id", form: "other-form" }
18
+ # )
19
+ #
20
+ # Found by installing the gem in an application that wires one select to
21
+ # another: choosing a client reloads that client's sites. The obvious way to
22
+ # do it is a Stimulus controller on the element whose `.value` is the
23
+ # selection and which fires `change` — the hidden input, and there was no way
24
+ # to name it. Attaching to the root works and is what that host settled on,
25
+ # but two things have no such workaround: `id`, which a `<label for>` or any
26
+ # external script needs, and `form`, which is how HTML lets a control submit
27
+ # with a form it does not sit inside.
28
+ #
29
+ # ## Why only the single-valued families
30
+ #
31
+ # Combobox in `multiple` mode, Slider with several thumbs and Calendar in
32
+ # `range` or `multiple` mode all render *several* hidden inputs. There is no
33
+ # single element there to be "the control's value", and copying a caller's
34
+ # attributes onto each is wrong in both of the ways that matter: `id` would
35
+ # be duplicated, which is invalid, and `data-controller` would connect the
36
+ # same controller once per input.
37
+ #
38
+ # Those families take their attributes on the root instead, which reaches
39
+ # them through `**attributes` and — since 0.2.1 — concatenates
40
+ # `data-controller` with the component's own rather than replacing it.
41
+ #
42
+ # ## Why the target is passed in rather than derived
43
+ #
44
+ # Each including component writes its own `data-shadcn--…-target` literally,
45
+ # even though the concern could work it out from the class name.
46
+ # `stimulus_contract_spec` finds targets by reading those literals out of the
47
+ # source; a derived one would be invisible to it, and the check that every
48
+ # target a component names exists in the JavaScript would quietly stop
49
+ # covering this input.
50
+ module SubmitsValue
51
+ def self.included(base)
52
+ base.class_eval do
53
+ attr_reader :input_attributes
54
+ end
55
+ end
56
+
57
+ def initialize(input_attributes: {}, **attributes)
58
+ @input_attributes = input_attributes || {}
59
+ super(**attributes)
60
+ end
61
+
62
+ private
63
+
64
+ # A caller cannot detach the input from the control it belongs to: `name`
65
+ # and `value` are asserted again after their attributes are merged in.
66
+ # Everything else is theirs, including the Stimulus target — unusual to
67
+ # replace, but it is their component to break.
68
+ #
69
+ # The re-assertion does not move those two keys. `Hash#merge` on a key that
70
+ # is already present updates the value and keeps its insertion position, so
71
+ # the attribute order the components emitted before this concern existed is
72
+ # unchanged — which is what keeps every rendered snapshot byte-identical.
73
+ def hidden_input_attributes(**defaults)
74
+ { "type" => "hidden", "name" => name, "value" => value }
75
+ .merge(defaults.transform_keys(&:to_s))
76
+ .merge(input_attributes.transform_keys(&:to_s))
77
+ .merge("name" => name, "value" => value)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module ContextMenu
5
+ # Port of registry/new-york-v4/ui/context-menu.tsx.
6
+ #
7
+ # Driven by `shadcn--dropdown-menu`, not a controller of its own: the two
8
+ # families declare the same slots and the menu's whole behaviour once open
9
+ # — roving focus, typeahead, submenus, checkbox and radio items — is the
10
+ # dropdown's. What differs is the way in, which is one action and one value.
11
+ class Component < ApplicationViewComponent
12
+ renders_one :trigger, "Shadcn::ContextMenu::Trigger::Component"
13
+ renders_one :menu_content, "Shadcn::ContextMenu::Content::Component"
14
+
15
+ slot_name :"context-menu"
16
+
17
+ attr_reader :loop
18
+
19
+ # `loop:` is the dropdown's own addition, kept here because both share the
20
+ # controller — Radix has it as a prop and shadcn passes it in neither file.
21
+ def initialize(loop: false, **attributes)
22
+ @loop = loop
23
+ super(**attributes)
24
+ end
25
+
26
+ def element_attributes(**defaults)
27
+ super(**{
28
+ style: merged_style(CONTENTS_STYLE),
29
+ "data-controller" => "shadcn--dropdown-menu",
30
+ "data-shadcn--dropdown-menu-prefix-value" => "context-menu",
31
+ "data-shadcn--dropdown-menu-loop-value" => loop,
32
+ # A context menu opens where the pointer is, so it has no side to
33
+ # prefer and no gap to leave: the point *is* the corner.
34
+ "data-shadcn--dropdown-menu-side-value" => "bottom",
35
+ "data-shadcn--dropdown-menu-align-value" => "start",
36
+ "data-shadcn--dropdown-menu-side-offset-value" => 0
37
+ }.merge(defaults))
38
+ end
39
+
40
+ def call
41
+ render_element(body: safe_join([ trigger, menu_content, content ].compact))
42
+ end
43
+ end
44
+ end
45
+ end