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,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module ScrollArea
5
+ module Viewport
6
+ # The box that actually scrolls. Native scrollbars are hidden on it —
7
+ # `shadcn.css` carries the rule Radix injects as a `<style>` tag
8
+ # (vendor/radix/ui/scroll-area.tsx:266-278), keyed on the slot rather than
9
+ # on a class, so a host cannot switch it off by accident and there is no
10
+ # token for `parity_spec` to miss.
11
+ #
12
+ # `overflow` is inline and per axis, as Radix writes it (`:222-223`):
13
+ # `scroll` on an axis with a scrollbar, `hidden` on one without. It has to
14
+ # be `scroll` rather than `auto` — the element must keep its scrollable
15
+ # geometry even when the custom bar is the only thing showing it.
16
+ class Component < ApplicationViewComponent
17
+ slot_name :"scroll-area-viewport"
18
+
19
+ style do
20
+ base {
21
+ "size-full rounded-[inherit] transition-[color,box-shadow] outline-none " \
22
+ "focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
23
+ }
24
+ end
25
+
26
+ attr_reader :horizontal, :vertical
27
+
28
+ def initialize(horizontal: false, vertical: true, **attributes)
29
+ @horizontal = horizontal
30
+ @vertical = vertical
31
+ super(**attributes)
32
+ end
33
+
34
+ # `tabindex: 0`, which Radix does **not** set — measured, there is no
35
+ # `tabIndex` anywhere in the primitive. A deliberate divergence, for
36
+ # three reasons that line up: axe fails a scrollable region with no
37
+ # keyboard access; Chrome and Firefox make scrollers focusable
38
+ # themselves and Safari does not, so without this the component's
39
+ # keyboard behaviour depends on the browser; and shadcn's own classes
40
+ # here style `focus-visible`, which only means anything on an element
41
+ # that can be focused. The sibling primitive shadcn publishes does set
42
+ # it on its viewport (`vendor/shadcn-react/message-scroller/components.tsx:209`).
43
+ def element_attributes(**defaults)
44
+ super(**{
45
+ tabindex: 0,
46
+ style: merged_style(
47
+ "overflow-x: #{horizontal ? 'scroll' : 'hidden'}; " \
48
+ "overflow-y: #{vertical ? 'scroll' : 'hidden'};"
49
+ ),
50
+ "data-shadcn--scroll-area-target" => "viewport"
51
+ }.merge(defaults))
52
+ end
53
+
54
+ # `display: table` with `min-width: 100%`, which is Radix's own
55
+ # (`:296-299`) and is the only reason content *size* can be measured at
56
+ # all: a block child matches the viewport's width and would report the
57
+ # same number however wide its content really is.
58
+ def call
59
+ render_element(
60
+ body: tag.div(
61
+ content,
62
+ style: "min-width: 100%; display: table;",
63
+ "data-shadcn--scroll-area-target": "content"
64
+ )
65
+ )
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # The one part of the ScrollArea family that is an element with a `data-slot`
5
+ # and fixed classes.
6
+ module ScrollArea
7
+ extend Parts
8
+
9
+ # Radix's `ScrollAreaThumb`. Its width, height and transform are inline and
10
+ # the controller's — the classes only make it a rounded bar.
11
+ part :thumb, slot: "scroll-area-thumb",
12
+ classes: "relative flex-1 rounded-full bg-border"
13
+ end
14
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Select
5
+ # Port of registry/new-york-v4/ui/select.tsx
6
+ #
7
+ # A `role="combobox"` trigger driving a `role="listbox"` layer. When `name:`
8
+ # is given a hidden input mirrors the selection, the way Radix's BubbleSelect
9
+ # does, so the control submits with the form.
10
+ class Component < ApplicationViewComponent
11
+ include Concerns::SubmitsValue
12
+ # Lambda slots so `searchable` reaches the two parts that change shape
13
+ # because of it — the same way ToggleGroup feeds variant and size to its
14
+ # items, and for the same reason React would use context.
15
+ renders_one :trigger, ->(**kwargs, &block) {
16
+ Trigger::Component.new(searchable:, **kwargs, &block)
17
+ }
18
+ renders_one :select_content, ->(**kwargs, &block) {
19
+ Content::Component.new(searchable:, **kwargs, &block)
20
+ }
21
+
22
+ slot_name :select
23
+
24
+ attr_reader :open, :value, :name, :placeholder, :side, :align, :side_offset, :searchable
25
+
26
+ def initialize(value: nil, name: nil, placeholder: nil, open: false,
27
+ side: :bottom, align: :center, side_offset: 4, searchable: false,
28
+ **attributes)
29
+ @searchable = searchable
30
+ @value = value.to_s
31
+ @name = name
32
+ @placeholder = placeholder.to_s
33
+ @open = open
34
+ @side = side&.to_sym || :bottom
35
+ @align = align&.to_sym || :center
36
+ @side_offset = side_offset
37
+ super(**attributes)
38
+ end
39
+
40
+ def element_attributes(**defaults)
41
+ super(**{
42
+ style: merged_style(CONTENTS_STYLE),
43
+ "data-controller" => "shadcn--select",
44
+ "data-shadcn--select-open-value" => open,
45
+ "data-shadcn--select-value-value" => value,
46
+ "data-shadcn--select-placeholder-value" => placeholder,
47
+ "data-shadcn--select-side-value" => side,
48
+ "data-shadcn--select-align-value" => align,
49
+ "data-shadcn--select-side-offset-value" => side_offset,
50
+ "data-shadcn--select-searchable-value" => searchable
51
+ }.merge(defaults))
52
+ end
53
+
54
+ def call
55
+ render_element(body: safe_join([ trigger, select_content, content, hidden_input ].compact))
56
+ end
57
+
58
+ private
59
+
60
+ def hidden_input
61
+ return unless name
62
+
63
+ tag.input(**hidden_input_attributes("data-shadcn--select-target" => "input"))
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Select
5
+ module Content
6
+ # SelectContent — the viewport and the two scroll buttons are part of the
7
+ # markup shadcn emits, so they are reproduced here.
8
+ class Component < ApplicationViewComponent
9
+ # `shrink-0` mirrors the `flexShrink: 0` Radix sets inline
10
+ # (vendor/radix/ui/select.tsx:1691): the buttons keep their height while
11
+ # the viewport takes the rest, which is what pins them to the edges.
12
+ SCROLL_BUTTON_CLASSES = "flex shrink-0 cursor-default items-center justify-center py-1"
13
+
14
+ renders_many :items, "Shadcn::Select::Item::Component"
15
+
16
+ slot_name :"select-content"
17
+
18
+ style do
19
+ base {
20
+ "relative z-50 flex flex-col max-h-(--radix-select-content-available-height) min-w-[8rem] " \
21
+ "origin-(--radix-select-content-transform-origin) overflow-x-hidden " \
22
+ "overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md " \
23
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 " \
24
+ "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 " \
25
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 " \
26
+ "data-[state=closed]:zoom-out-95 data-[state=open]:animate-in " \
27
+ "data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95"
28
+ }
29
+
30
+ variants {
31
+ position {
32
+ popper {
33
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 " \
34
+ "data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1"
35
+ }
36
+ send(:"item-aligned") { "" }
37
+ }
38
+ }
39
+
40
+ defaults { { position: :"item-aligned" } }
41
+ end
42
+
43
+ attr_reader :position, :searchable
44
+
45
+ def initialize(position: :"item-aligned", searchable: false, **attributes)
46
+ @position = position&.to_sym || :"item-aligned"
47
+ @searchable = searchable
48
+ super(**attributes)
49
+ end
50
+
51
+ def style_variants
52
+ { position: }
53
+ end
54
+
55
+ def element_attributes(**defaults)
56
+ super(**{
57
+ # A searchable popover holds a text field *and* a list, so it
58
+ # cannot be the list: a textbox is not an allowed child of a
59
+ # listbox. The role moves to Select::List and this becomes the
60
+ # dialog that contains both, which is what axe accepts and what
61
+ # shadcn's aria variant emits.
62
+ role: (searchable ? "dialog" : "listbox"),
63
+ "aria-label" => (shadcn_t("select.dialog_label") if searchable),
64
+ tabindex: "-1",
65
+ "data-state" => "closed",
66
+ hidden: true,
67
+ "data-shadcn--select-target" => "content",
68
+ # `scroll` does not bubble, but Stimulus binds the action to this
69
+ # element itself, so it fires. Which element actually scrolls
70
+ # differs by mode — see the controller's `scrollContainer`.
71
+ "data-action" => "keydown->shadcn--select#contentKeydown"
72
+ }.merge(defaults))
73
+ end
74
+
75
+ def call
76
+ render_element(body: safe_join([
77
+ scroll_button("select-scroll-up-button", "chevron-up", "scrollUpButton"),
78
+ viewport,
79
+ scroll_button("select-scroll-down-button", "chevron-down", "scrollDownButton")
80
+ ]))
81
+ end
82
+
83
+ private
84
+
85
+ def viewport
86
+ tag.div(
87
+ searchable ? searchable_body : options,
88
+ class: viewport_classes,
89
+ "data-shadcn--select-target": "viewport",
90
+ # `scroll` does not bubble, but Stimulus binds to this element
91
+ # itself, so it fires. In a searchable panel the list scrolls
92
+ # instead and carries its own copy of this action.
93
+ "data-action": "scroll->shadcn--select#syncScrollButtons"
94
+ )
95
+ end
96
+
97
+ # The field is pinned and the list scrolls under it, rather than the
98
+ # whole popover scrolling and taking the field with it.
99
+ def searchable_body
100
+ safe_join([
101
+ render(Search::Component.new),
102
+ render(List::Component.new) { options },
103
+ render(Empty::Component.new)
104
+ ])
105
+ end
106
+
107
+ def options
108
+ safe_join([ items, content ].flatten.compact)
109
+ end
110
+
111
+ # Radix puts the scrolling here, not on the content: `position: relative`
112
+ # so an item's offsetTop is measured against the viewport rather than
113
+ # the scroll-up button, `flex: 1` to take the space the buttons leave,
114
+ # and `overflow: hidden auto` (vendor/radix/ui/select.tsx:1240-1247).
115
+ # Without it the content scrolled and carried the buttons out of sight
116
+ # with the options — see decisions/04-bugs-fixed.md.
117
+ VIEWPORT_CLASSES = "relative flex-1 overflow-x-hidden overflow-y-auto"
118
+
119
+ def viewport_classes
120
+ # A searchable panel pins its search field, so the scrolling moves one
121
+ # level further in, onto the list.
122
+ return ShadcnViewComponent.cn("relative flex-1 flex flex-col overflow-hidden") if searchable
123
+
124
+ extra =
125
+ if position == :popper
126
+ "h-[var(--radix-select-trigger-height)] w-full " \
127
+ "min-w-[var(--radix-select-trigger-width)] scroll-my-1"
128
+ end
129
+
130
+ ShadcnViewComponent.cn(VIEWPORT_CLASSES, "p-1", extra)
131
+ end
132
+
133
+ # Radix mounts these only while there is somewhere to scroll and
134
+ # unmounts them otherwise (vendor/radix/ui/select.tsx:1594, :1642).
135
+ # Rendered hidden here instead, which is this gem's convention for the
136
+ # same thing — see decisions/02-javascript.md, "Indicators are rendered
137
+ # hidden, not omitted" — so the controller only toggles a flag.
138
+ #
139
+ # `aria-hidden` because they are a pointer affordance: the keyboard
140
+ # scrolls by moving the highlight (vendor/radix/ui/select.tsx:1691).
141
+ def scroll_button(slot, icon, target)
142
+ tag.div(
143
+ render(Icon::Component.new(icon, class: "size-4")),
144
+ "data-slot": slot,
145
+ "aria-hidden": true,
146
+ hidden: true,
147
+ "data-shadcn--select-target": target,
148
+ "data-action": "pointerdown->shadcn--select#startAutoScroll " \
149
+ "pointermove->shadcn--select#startAutoScroll " \
150
+ "pointerleave->shadcn--select#stopAutoScroll",
151
+ class: SCROLL_BUTTON_CLASSES
152
+ )
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Select
5
+ module Empty
6
+ # Shown when the filter matches nothing.
7
+ #
8
+ # A *sibling* of the listbox rather than a child of it. shadcn's aria
9
+ # variant nests its empty state inside the list and gives it
10
+ # `role="option"` — an option that cannot be chosen — which is how React
11
+ # Aria's collection renders one. Outside the list the question does not
12
+ # arise, and a non-option inside a listbox is precisely the shape axe
13
+ # rejected while this component's structure was being chosen.
14
+ #
15
+ # The classes are this gem's own. Upstream's are a single
16
+ # `cn-select-empty-aria`, defined across the themed sheets in
17
+ # `registry/styles/`, which this gem does not ship.
18
+ class Component < ApplicationViewComponent
19
+ slot_name :"select-empty"
20
+
21
+ style do
22
+ base { "py-6 text-center text-sm text-muted-foreground" }
23
+ end
24
+
25
+ def element_attributes(**defaults)
26
+ super(**{
27
+ hidden: true,
28
+ "data-shadcn--select-target" => "empty"
29
+ }.merge(defaults))
30
+ end
31
+
32
+ def call
33
+ render_element(body: content.presence || shadcn_t("select.empty"))
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Select
5
+ module Group
6
+ # SelectGroup
7
+ class Component < ApplicationViewComponent
8
+ slot_name :"select-group"
9
+
10
+ def element_attributes(**defaults)
11
+ super(**{ role: "group" }.merge(defaults))
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Select
5
+ module Item
6
+ # SelectItem
7
+ class Component < ApplicationViewComponent
8
+ INDICATOR_CLASSES = "absolute right-2 flex size-3.5 items-center justify-center"
9
+
10
+ slot_name :"select-item"
11
+
12
+ style do
13
+ base {
14
+ "relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 " \
15
+ "pl-2 text-sm outline-hidden select-none focus:bg-accent " \
16
+ "focus:text-accent-foreground data-[highlighted]:bg-accent " \
17
+ "data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none " \
18
+ "data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 " \
19
+ "[&_svg:not([class*='size-'])]:size-4 " \
20
+ "[&_svg:not([class*='text-'])]:text-muted-foreground " \
21
+ "*:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2"
22
+ }
23
+ end
24
+
25
+ attr_reader :value, :selected, :disabled
26
+
27
+ def initialize(value:, selected: false, disabled: false, **attributes)
28
+ @value = value.to_s
29
+ @selected = selected
30
+ @disabled = disabled
31
+ super(**attributes)
32
+ end
33
+
34
+ def element_attributes(**defaults)
35
+ super(**{
36
+ role: "option",
37
+ tabindex: "-1",
38
+ "aria-selected" => selected.to_s,
39
+ "data-value" => value,
40
+ "data-state" => (selected ? "checked" : "unchecked"),
41
+ "data-disabled" => (true if disabled),
42
+ "data-shadcn--select-target" => "item",
43
+ "data-action" => "click->shadcn--select#select " \
44
+ "pointerenter->shadcn--select#pointerenter"
45
+ }.merge(defaults))
46
+ end
47
+
48
+ def call
49
+ render_element(body: safe_join([ indicator, item_text ]))
50
+ end
51
+
52
+ private
53
+
54
+ def indicator
55
+ tag.span(
56
+ tag.span(
57
+ render(Icon::Component.new("check", class: "size-4")),
58
+ hidden: (true unless selected)
59
+ ),
60
+ "data-slot": "select-item-indicator",
61
+ class: INDICATOR_CLASSES
62
+ )
63
+ end
64
+
65
+ def item_text
66
+ tag.span(content, "data-slot": "select-item-text")
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Select
5
+ module List
6
+ # Where the options live once a search field shares the popover.
7
+ #
8
+ # In the plain select the listbox role sits on SelectContent itself. It
9
+ # cannot when a text field is in there too: a textbox is not an allowed
10
+ # child of a listbox, and axe reports `aria-required-children` — critical —
11
+ # for exactly that shape. shadcn's aria variant splits them the same way.
12
+ #
13
+ # Classes are that variant's verbatim, except `p-1` for its `p-0`: the
14
+ # padding this port keeps on the viewport moves here, because the viewport
15
+ # now holds the search field too.
16
+ class Component < ApplicationViewComponent
17
+ slot_name :"select-list"
18
+
19
+ style do
20
+ base {
21
+ "group/select-list max-h-[inherit] overflow-x-hidden overflow-y-auto p-1 outline-hidden"
22
+ }
23
+ end
24
+
25
+ def element_attributes(**defaults)
26
+ # A named listbox, because an unnamed one is an
27
+ # `aria-input-field-name` violation — serious, and WCAG rather than
28
+ # best-practice. The dialog around it is named separately, so a
29
+ # screen reader announces the popover and then the list rather than
30
+ # the same words twice. shadcn's aria variant names both too.
31
+ super(**{
32
+ role: "listbox",
33
+ "data-action" => "scroll->shadcn--select#syncScrollButtons",
34
+ "aria-label" => shadcn_t("select.list_label"),
35
+ "data-shadcn--select-target" => "list"
36
+ }.merge(defaults))
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Select
5
+ class Preview < ApplicationViewComponentPreview
6
+ def default
7
+ render_with_template
8
+ end
9
+
10
+ def searchable
11
+ render_with_template
12
+ end
13
+
14
+ def scrollable
15
+ render_with_template
16
+ end
17
+
18
+ # Upstream's "Disabled" — as a disabled *item*, which is the half a listbox has to answer for — and "Invalid".
19
+ def states
20
+ render_with_template
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ <%= render(Shadcn::Select::Component.new(name: "fruit", placeholder: "Select a fruit")) do |s| %>
2
+ <%# `role="combobox"` forbids taking the name from its own content. %>
3
+ <% s.with_trigger(class: "w-[200px]", "aria-label": "Favourite fruit") do |tr| %>
4
+ <% tr.with_value(placeholder: "Select a fruit") %>
5
+ <% end %>
6
+ <%# Items go in the block rather than through the `item` slot, so the group
7
+ label keeps its place above them: slot content renders before block
8
+ content. %>
9
+ <% s.with_select_content do %>
10
+ <%= render(Shadcn::Select::Group::Component.new) do %>
11
+ <%= render(Shadcn::Select::Label::Component.new) { "Fruits" } %>
12
+ <% %w[Apple Banana Blueberry Grapes Pineapple].each do |fruit| %>
13
+ <%= render(Shadcn::Select::Item::Component.new(value: fruit.downcase)) { fruit } %>
14
+ <% end %>
15
+ <% end %>
16
+ <% end %>
17
+ <% end %>
@@ -0,0 +1,19 @@
1
+ <%# Long enough to overflow the popover, which is what puts the two scroll
2
+ buttons to work: they stay hidden while there is nowhere to scroll and
3
+ appear per direction, as Radix's do. %>
4
+ <%= render(Shadcn::Select::Component.new(name: "timezone", placeholder: "Select a timezone")) do |s| %>
5
+ <% s.with_trigger(class: "w-[240px]", "aria-label": "Timezone") do |tr| %>
6
+ <% tr.with_value(placeholder: "Select a timezone") %>
7
+ <% end %>
8
+ <% s.with_select_content do %>
9
+ <% [
10
+ "Auckland", "Bangkok", "Berlin", "Cairo", "Chicago", "Denver",
11
+ "Dublin", "Helsinki", "Istanbul", "Jakarta", "Karachi", "Lagos",
12
+ "Lima", "Lisbon", "London", "Madrid", "Manila", "Melbourne",
13
+ "Mexico City", "Moscow", "Nairobi", "New York", "Oslo", "Paris",
14
+ "Rome", "Seoul", "Singapore", "Sydney", "Tokyo", "Toronto"
15
+ ].each do |city| %>
16
+ <%= render(Shadcn::Select::Item::Component.new(value: city.parameterize)) { city } %>
17
+ <% end %>
18
+ <% end %>
19
+ <% end %>
@@ -0,0 +1,18 @@
1
+ <%# The trigger is a plain button here rather than a combobox, so it could take
2
+ its name from its own text — but naming it explicitly is what the FormBuilder
3
+ does for every select, and a preview is documentation. %>
4
+ <%= render(Shadcn::Select::Component.new(name: "fruit", placeholder: "Select a fruit", searchable: true)) do |s| %>
5
+ <% s.with_trigger(class: "w-[220px]", "aria-label": "Favourite fruit") do |tr| %>
6
+ <% tr.with_value(placeholder: "Select a fruit") %>
7
+ <% end %>
8
+ <%# Items go in the block rather than through the `item` slot, so anything
9
+ rendered beside them keeps its place — the same reason default.html.erb
10
+ does it. %>
11
+ <% s.with_select_content do %>
12
+ <%= render(Shadcn::Select::Item::Component.new(value: "apple")) { "Apple" } %>
13
+ <%= render(Shadcn::Select::Item::Component.new(value: "banana")) { "Banana" } %>
14
+ <%= render(Shadcn::Select::Item::Component.new(value: "blueberry")) { "Blueberry" } %>
15
+ <%= render(Shadcn::Select::Item::Component.new(value: "grapes")) { "Grapes" } %>
16
+ <%= render(Shadcn::Select::Item::Component.new(value: "pineapple")) { "Pineapple" } %>
17
+ <% end %>
18
+ <% end %>
@@ -0,0 +1,25 @@
1
+ <%# Upstream's "Disabled" and "Invalid". The first is a *disabled item* as well
2
+ as a disabled trigger — the two are different things and only the item
3
+ version shows what a listbox does with one. %>
4
+ <div class="flex items-start gap-4">
5
+ <%= render(Shadcn::Select::Component.new(name: "plan", placeholder: "Choose a plan")) do |s| %>
6
+ <% s.with_trigger(class: "w-[200px]", "aria-label": "Plan") do |tr| %>
7
+ <% tr.with_value(placeholder: "Choose a plan") %>
8
+ <% end %>
9
+ <% s.with_select_content do %>
10
+ <%= render(Shadcn::Select::Item::Component.new(value: "free")) { "Free" } %>
11
+ <%= render(Shadcn::Select::Item::Component.new(value: "pro")) { "Pro" } %>
12
+ <%= render(Shadcn::Select::Item::Component.new(value: "enterprise", disabled: true)) { "Enterprise — sales only" } %>
13
+ <% end %>
14
+ <% end %>
15
+
16
+ <%= render(Shadcn::Select::Component.new(name: "region", placeholder: "Pick a region")) do |s| %>
17
+ <% s.with_trigger(class: "w-[200px]", "aria-label": "Region", "aria-invalid": "true") do |tr| %>
18
+ <% tr.with_value(placeholder: "Pick a region") %>
19
+ <% end %>
20
+ <% s.with_select_content do %>
21
+ <%= render(Shadcn::Select::Item::Component.new(value: "eu")) { "Europe" } %>
22
+ <%= render(Shadcn::Select::Item::Component.new(value: "us")) { "United States" } %>
23
+ <% end %>
24
+ <% end %>
25
+ </div>
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module Select
5
+ module Search
6
+ # The filter field, composing the already-ported InputGroup the way
7
+ # shadcn's aria variant does — its popover renders `data-slot="input-group"`
8
+ # around the control, with the magnifier as an addon.
9
+ #
10
+ # Two things it does *not* copy from that variant, both with reasons in
11
+ # decisions/01-architecture.md: the control keeps `input-group-control`
12
+ # rather than being restamped `select-input`, because this port's group
13
+ # raises its focus ring off that slot name and the aria one does not use
14
+ # `has-[[data-slot=…]]` selectors at all; and the field carries an
15
+ # accessible name, where upstream's has none — axe calls an unnamed one a
16
+ # critical `label` violation.
17
+ class Component < ApplicationViewComponent
18
+ slot_name :"select-input-wrapper"
19
+
20
+ style do
21
+ base { "p-1 pb-0" }
22
+ end
23
+
24
+ attr_reader :label
25
+
26
+ def initialize(label: nil, **attributes)
27
+ @label = label
28
+ super(**attributes)
29
+ end
30
+
31
+ def call
32
+ render_element(body: render(InputGroup::Component.new) { safe_join([ field, addon ]) })
33
+ end
34
+
35
+ private
36
+
37
+ def field
38
+ render(InputGroup::Input::Component.new(
39
+ "aria-label": label || shadcn_t("select.search_label"),
40
+ "aria-autocomplete": "list",
41
+ "data-shadcn--select-target": "search",
42
+ "data-action": "input->shadcn--select#search"
43
+ ))
44
+ end
45
+
46
+ def addon
47
+ render(InputGroup::Addon::Component.new) do
48
+ render(Icon::Component.new("search", class: "size-4"))
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end