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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +255 -0
- data/MIT-LICENSE +20 -0
- data/README.md +604 -0
- data/app/assets/stylesheets/shadcn-themes.css +1075 -0
- data/app/assets/stylesheets/shadcn.css +853 -0
- data/app/components/shadcn/accordion/component.rb +44 -0
- data/app/components/shadcn/accordion/content/component.rb +44 -0
- data/app/components/shadcn/accordion/item/component.rb +41 -0
- data/app/components/shadcn/accordion/preview.rb +16 -0
- data/app/components/shadcn/accordion/previews/default.html.erb +10 -0
- data/app/components/shadcn/accordion/previews/multiple.html.erb +13 -0
- data/app/components/shadcn/accordion/trigger/component.rb +52 -0
- data/app/components/shadcn/alert/component.rb +52 -0
- data/app/components/shadcn/alert/preview.rb +11 -0
- data/app/components/shadcn/alert/previews/default.html.erb +8 -0
- data/app/components/shadcn/alert.rb +17 -0
- data/app/components/shadcn/alert_dialog/action/component.rb +35 -0
- data/app/components/shadcn/alert_dialog/cancel/component.rb +16 -0
- data/app/components/shadcn/alert_dialog/component.rb +20 -0
- data/app/components/shadcn/alert_dialog/content/component.rb +55 -0
- data/app/components/shadcn/alert_dialog/header/component.rb +33 -0
- data/app/components/shadcn/alert_dialog/preview.rb +16 -0
- data/app/components/shadcn/alert_dialog/previews/default.html.erb +13 -0
- data/app/components/shadcn/alert_dialog/previews/small_with_media.html.erb +38 -0
- data/app/components/shadcn/alert_dialog.rb +32 -0
- data/app/components/shadcn/application_view_component.rb +225 -0
- data/app/components/shadcn/application_view_component_preview.rb +8 -0
- data/app/components/shadcn/aspect_ratio/component.rb +34 -0
- data/app/components/shadcn/aspect_ratio/preview.rb +16 -0
- data/app/components/shadcn/aspect_ratio/previews/default.html.erb +5 -0
- data/app/components/shadcn/aspect_ratio/previews/ratios.html.erb +12 -0
- data/app/components/shadcn/attachment/action/component.rb +19 -0
- data/app/components/shadcn/attachment/component.rb +76 -0
- data/app/components/shadcn/attachment/media/component.rb +56 -0
- data/app/components/shadcn/attachment/preview.rb +11 -0
- data/app/components/shadcn/attachment/previews/default.html.erb +80 -0
- data/app/components/shadcn/attachment/trigger/component.rb +31 -0
- data/app/components/shadcn/attachment.rb +42 -0
- data/app/components/shadcn/avatar/component.rb +43 -0
- data/app/components/shadcn/avatar/fallback/component.rb +23 -0
- data/app/components/shadcn/avatar/image/component.rb +20 -0
- data/app/components/shadcn/avatar/preview.rb +11 -0
- data/app/components/shadcn/avatar/previews/default.html.erb +13 -0
- data/app/components/shadcn/avatar.rb +32 -0
- data/app/components/shadcn/badge/component.rb +57 -0
- data/app/components/shadcn/badge/preview.rb +11 -0
- data/app/components/shadcn/badge/previews/default.html.erb +5 -0
- data/app/components/shadcn/breadcrumb/component.rb +15 -0
- data/app/components/shadcn/breadcrumb/ellipsis/component.rb +28 -0
- data/app/components/shadcn/breadcrumb/page/component.rb +25 -0
- data/app/components/shadcn/breadcrumb/preview.rb +16 -0
- data/app/components/shadcn/breadcrumb/previews/custom_separator.html.erb +17 -0
- data/app/components/shadcn/breadcrumb/previews/default.html.erb +15 -0
- data/app/components/shadcn/breadcrumb/separator/component.rb +31 -0
- data/app/components/shadcn/breadcrumb.rb +18 -0
- data/app/components/shadcn/bubble/component.rb +90 -0
- data/app/components/shadcn/bubble/preview.rb +16 -0
- data/app/components/shadcn/bubble/previews/alignment.html.erb +33 -0
- data/app/components/shadcn/bubble/previews/default.html.erb +22 -0
- data/app/components/shadcn/bubble/reactions/component.rb +54 -0
- data/app/components/shadcn/bubble.rb +24 -0
- data/app/components/shadcn/button/component.rb +85 -0
- data/app/components/shadcn/button/preview.rb +31 -0
- data/app/components/shadcn/button/previews/default.html.erb +8 -0
- data/app/components/shadcn/button/previews/sizes.html.erb +5 -0
- data/app/components/shadcn/button/previews/variants.html.erb +5 -0
- data/app/components/shadcn/button_group/component.rb +53 -0
- data/app/components/shadcn/button_group/preview.rb +15 -0
- data/app/components/shadcn/button_group/previews/default.html.erb +18 -0
- data/app/components/shadcn/button_group/previews/vertical.html.erb +5 -0
- data/app/components/shadcn/button_group/separator/component.rb +30 -0
- data/app/components/shadcn/button_group/text/component.rb +20 -0
- data/app/components/shadcn/calendar/component.rb +558 -0
- data/app/components/shadcn/calendar/month.rb +74 -0
- data/app/components/shadcn/calendar/preview.rb +64 -0
- data/app/components/shadcn/calendar/previews/booked.html.erb +18 -0
- data/app/components/shadcn/calendar/previews/custom_cell_size.html.erb +11 -0
- data/app/components/shadcn/calendar/previews/date_and_time.html.erb +31 -0
- data/app/components/shadcn/calendar/previews/default.html.erb +8 -0
- data/app/components/shadcn/calendar/previews/dropdown_caption.html.erb +11 -0
- data/app/components/shadcn/calendar/previews/in_a_form.html.erb +8 -0
- data/app/components/shadcn/calendar/previews/presets.html.erb +20 -0
- data/app/components/shadcn/calendar/previews/range.html.erb +10 -0
- data/app/components/shadcn/calendar/previews/week_numbers.html.erb +8 -0
- data/app/components/shadcn/card/component.rb +24 -0
- data/app/components/shadcn/card/header/component.rb +26 -0
- data/app/components/shadcn/card/preview.rb +16 -0
- data/app/components/shadcn/card/previews/default.html.erb +18 -0
- data/app/components/shadcn/card/previews/image.html.erb +37 -0
- data/app/components/shadcn/card.rb +21 -0
- data/app/components/shadcn/carousel/component.rb +61 -0
- data/app/components/shadcn/carousel/content/component.rb +86 -0
- data/app/components/shadcn/carousel/item/component.rb +42 -0
- data/app/components/shadcn/carousel/next/component.rb +59 -0
- data/app/components/shadcn/carousel/preview.rb +22 -0
- data/app/components/shadcn/carousel/previews/default.html.erb +22 -0
- data/app/components/shadcn/carousel/previews/sizes.html.erb +42 -0
- data/app/components/shadcn/carousel/previews/vertical.html.erb +27 -0
- data/app/components/shadcn/carousel/previous/component.rb +59 -0
- data/app/components/shadcn/chart/area/component.rb +43 -0
- data/app/components/shadcn/chart/bar/component.rb +75 -0
- data/app/components/shadcn/chart/cartesian/component.rb +152 -0
- data/app/components/shadcn/chart/component.rb +124 -0
- data/app/components/shadcn/chart/focusable.rb +38 -0
- data/app/components/shadcn/chart/legend/component.rb +55 -0
- data/app/components/shadcn/chart/line/component.rb +50 -0
- data/app/components/shadcn/chart/pie/component.rb +169 -0
- data/app/components/shadcn/chart/plot.rb +147 -0
- data/app/components/shadcn/chart/preview.rb +45 -0
- data/app/components/shadcn/chart/previews/area.html.erb +31 -0
- data/app/components/shadcn/chart/previews/bar.html.erb +32 -0
- data/app/components/shadcn/chart/previews/bar_stacked.html.erb +30 -0
- data/app/components/shadcn/chart/previews/default.html.erb +30 -0
- data/app/components/shadcn/chart/previews/donut.html.erb +16 -0
- data/app/components/shadcn/chart/previews/line.html.erb +30 -0
- data/app/components/shadcn/chart/previews/percentages.html.erb +17 -0
- data/app/components/shadcn/chart/table/component.rb +75 -0
- data/app/components/shadcn/chart/tooltip/component.rb +111 -0
- data/app/components/shadcn/checkbox/component.rb +109 -0
- data/app/components/shadcn/checkbox/preview.rb +16 -0
- data/app/components/shadcn/checkbox/previews/default.html.erb +14 -0
- data/app/components/shadcn/checkbox/previews/states.html.erb +23 -0
- data/app/components/shadcn/collapsible/component.rb +35 -0
- data/app/components/shadcn/collapsible/content/component.rb +20 -0
- data/app/components/shadcn/collapsible/preview.rb +11 -0
- data/app/components/shadcn/collapsible/previews/default.html.erb +9 -0
- data/app/components/shadcn/collapsible/trigger/component.rb +23 -0
- data/app/components/shadcn/combobox/chip/component.rb +60 -0
- data/app/components/shadcn/combobox/chip_input/component.rb +43 -0
- data/app/components/shadcn/combobox/chips/component.rb +54 -0
- data/app/components/shadcn/combobox/clear/component.rb +32 -0
- data/app/components/shadcn/combobox/component.rb +113 -0
- data/app/components/shadcn/combobox/content/component.rb +59 -0
- data/app/components/shadcn/combobox/empty/component.rb +23 -0
- data/app/components/shadcn/combobox/input/component.rb +85 -0
- data/app/components/shadcn/combobox/item/component.rb +69 -0
- data/app/components/shadcn/combobox/list/component.rb +27 -0
- data/app/components/shadcn/combobox/preview.rb +31 -0
- data/app/components/shadcn/combobox/previews/default.html.erb +20 -0
- data/app/components/shadcn/combobox/previews/grouped.html.erb +25 -0
- data/app/components/shadcn/combobox/previews/multiple.html.erb +28 -0
- data/app/components/shadcn/combobox/previews/with_clear.html.erb +16 -0
- data/app/components/shadcn/combobox/trigger/component.rb +54 -0
- data/app/components/shadcn/combobox.rb +16 -0
- data/app/components/shadcn/command/component.rb +67 -0
- data/app/components/shadcn/command/dialog/component.rb +72 -0
- data/app/components/shadcn/command/empty/component.rb +29 -0
- data/app/components/shadcn/command/group/component.rb +63 -0
- data/app/components/shadcn/command/input/component.rb +66 -0
- data/app/components/shadcn/command/item/component.rb +62 -0
- data/app/components/shadcn/command/list/component.rb +36 -0
- data/app/components/shadcn/command/preview.rb +26 -0
- data/app/components/shadcn/command/previews/default.html.erb +41 -0
- data/app/components/shadcn/command/previews/dialog.html.erb +33 -0
- data/app/components/shadcn/command/previews/ranking.html.erb +16 -0
- data/app/components/shadcn/command/separator/component.rb +33 -0
- data/app/components/shadcn/command.rb +14 -0
- data/app/components/shadcn/concerns/modal_root.rb +43 -0
- data/app/components/shadcn/concerns/submits_value.rb +81 -0
- data/app/components/shadcn/context_menu/component.rb +45 -0
- data/app/components/shadcn/context_menu/content/component.rb +40 -0
- data/app/components/shadcn/context_menu/preview.rb +16 -0
- data/app/components/shadcn/context_menu/previews/default.html.erb +34 -0
- data/app/components/shadcn/context_menu/previews/items.html.erb +35 -0
- data/app/components/shadcn/context_menu/sub/component.rb +47 -0
- data/app/components/shadcn/context_menu/sub_content/component.rb +31 -0
- data/app/components/shadcn/context_menu/trigger/component.rb +50 -0
- data/app/components/shadcn/context_menu.rb +43 -0
- data/app/components/shadcn/dialog/close/component.rb +20 -0
- data/app/components/shadcn/dialog/component.rb +20 -0
- data/app/components/shadcn/dialog/content/component.rb +78 -0
- data/app/components/shadcn/dialog/header/component.rb +23 -0
- data/app/components/shadcn/dialog/overlay/component.rb +28 -0
- data/app/components/shadcn/dialog/preview.rb +28 -0
- data/app/components/shadcn/dialog/previews/default.html.erb +17 -0
- data/app/components/shadcn/dialog/previews/inside_stacking_context.html.erb +25 -0
- data/app/components/shadcn/dialog/previews/no_close_button.html.erb +15 -0
- data/app/components/shadcn/dialog/previews/scrollable_content.html.erb +29 -0
- data/app/components/shadcn/dialog/trigger/component.rb +24 -0
- data/app/components/shadcn/dialog.rb +17 -0
- data/app/components/shadcn/drawer/component.rb +33 -0
- data/app/components/shadcn/drawer/content/component.rb +95 -0
- data/app/components/shadcn/drawer/header/component.rb +34 -0
- data/app/components/shadcn/drawer/preview.rb +29 -0
- data/app/components/shadcn/drawer/previews/default.html.erb +21 -0
- data/app/components/shadcn/drawer/previews/scrollable.html.erb +20 -0
- data/app/components/shadcn/drawer/previews/scrollable_side.html.erb +21 -0
- data/app/components/shadcn/drawer/previews/sides.html.erb +17 -0
- data/app/components/shadcn/drawer.rb +27 -0
- data/app/components/shadcn/dropdown_menu/checkbox_item/component.rb +61 -0
- data/app/components/shadcn/dropdown_menu/component.rb +48 -0
- data/app/components/shadcn/dropdown_menu/content/component.rb +49 -0
- data/app/components/shadcn/dropdown_menu/group/component.rb +16 -0
- data/app/components/shadcn/dropdown_menu/item/component.rb +50 -0
- data/app/components/shadcn/dropdown_menu/preview.rb +16 -0
- data/app/components/shadcn/dropdown_menu/previews/checkboxes_and_radio.html.erb +34 -0
- data/app/components/shadcn/dropdown_menu/previews/default.html.erb +23 -0
- data/app/components/shadcn/dropdown_menu/radio_group/component.rb +16 -0
- data/app/components/shadcn/dropdown_menu/radio_item/component.rb +63 -0
- data/app/components/shadcn/dropdown_menu/sub/component.rb +44 -0
- data/app/components/shadcn/dropdown_menu/sub_content/component.rb +45 -0
- data/app/components/shadcn/dropdown_menu/sub_trigger/component.rb +58 -0
- data/app/components/shadcn/dropdown_menu/trigger/component.rb +25 -0
- data/app/components/shadcn/dropdown_menu.rb +18 -0
- data/app/components/shadcn/empty/component.rb +17 -0
- data/app/components/shadcn/empty/media/component.rb +48 -0
- data/app/components/shadcn/empty/preview.rb +16 -0
- data/app/components/shadcn/empty/previews/default.html.erb +15 -0
- data/app/components/shadcn/empty/previews/media.html.erb +32 -0
- data/app/components/shadcn/empty.rb +26 -0
- data/app/components/shadcn/field/component.rb +61 -0
- data/app/components/shadcn/field/error/component.rb +49 -0
- data/app/components/shadcn/field/legend/component.rb +30 -0
- data/app/components/shadcn/field/preview.rb +17 -0
- data/app/components/shadcn/field/previews/default.html.erb +29 -0
- data/app/components/shadcn/field/previews/form_builder.html.erb +38 -0
- data/app/components/shadcn/field/separator/component.rb +35 -0
- data/app/components/shadcn/field.rb +44 -0
- data/app/components/shadcn/hover_card/component.rb +51 -0
- data/app/components/shadcn/hover_card/content/component.rb +45 -0
- data/app/components/shadcn/hover_card/preview.rb +16 -0
- data/app/components/shadcn/hover_card/previews/default.html.erb +36 -0
- data/app/components/shadcn/hover_card/previews/sides.html.erb +15 -0
- data/app/components/shadcn/hover_card/trigger/component.rb +33 -0
- data/app/components/shadcn/icon/component.rb +76 -0
- data/app/components/shadcn/icon.rb +43 -0
- data/app/components/shadcn/input/component.rb +26 -0
- data/app/components/shadcn/input/preview.rb +16 -0
- data/app/components/shadcn/input/previews/default.html.erb +5 -0
- data/app/components/shadcn/input/previews/states.html.erb +20 -0
- data/app/components/shadcn/input_group/addon/component.rb +65 -0
- data/app/components/shadcn/input_group/button/component.rb +40 -0
- data/app/components/shadcn/input_group/component.rb +43 -0
- data/app/components/shadcn/input_group/input/component.rb +25 -0
- data/app/components/shadcn/input_group/preview.rb +17 -0
- data/app/components/shadcn/input_group/previews/addons.html.erb +53 -0
- data/app/components/shadcn/input_group/previews/default.html.erb +25 -0
- data/app/components/shadcn/input_group/text/component.rb +20 -0
- data/app/components/shadcn/input_group/textarea/component.rb +21 -0
- data/app/components/shadcn/input_otp/component.rb +95 -0
- data/app/components/shadcn/input_otp/preview.rb +17 -0
- data/app/components/shadcn/input_otp/previews/default.html.erb +19 -0
- data/app/components/shadcn/input_otp/previews/variations.html.erb +33 -0
- data/app/components/shadcn/input_otp/separator/component.rb +21 -0
- data/app/components/shadcn/input_otp/slot/component.rb +60 -0
- data/app/components/shadcn/input_otp.rb +11 -0
- data/app/components/shadcn/item/component.rb +56 -0
- data/app/components/shadcn/item/group/component.rb +53 -0
- data/app/components/shadcn/item/media/component.rb +53 -0
- data/app/components/shadcn/item/preview.rb +20 -0
- data/app/components/shadcn/item/previews/default.html.erb +32 -0
- data/app/components/shadcn/item/previews/link.html.erb +39 -0
- data/app/components/shadcn/item/previews/outline.html.erb +8 -0
- data/app/components/shadcn/item/separator/component.rb +24 -0
- data/app/components/shadcn/item.rb +34 -0
- data/app/components/shadcn/kbd/component.rb +23 -0
- data/app/components/shadcn/kbd/group/component.rb +17 -0
- data/app/components/shadcn/kbd/preview.rb +11 -0
- data/app/components/shadcn/kbd/previews/default.html.erb +4 -0
- data/app/components/shadcn/label/component.rb +20 -0
- data/app/components/shadcn/label/preview.rb +11 -0
- data/app/components/shadcn/label/previews/default.html.erb +4 -0
- data/app/components/shadcn/marker/component.rb +46 -0
- data/app/components/shadcn/marker/icon/component.rb +23 -0
- data/app/components/shadcn/marker/preview.rb +17 -0
- data/app/components/shadcn/marker/previews/default.html.erb +18 -0
- data/app/components/shadcn/marker/previews/status.html.erb +32 -0
- data/app/components/shadcn/marker.rb +17 -0
- data/app/components/shadcn/menubar/component.rb +36 -0
- data/app/components/shadcn/menubar/content/component.rb +49 -0
- data/app/components/shadcn/menubar/menu/component.rb +39 -0
- data/app/components/shadcn/menubar/preview.rb +16 -0
- data/app/components/shadcn/menubar/previews/default.html.erb +52 -0
- data/app/components/shadcn/menubar/previews/items.html.erb +38 -0
- data/app/components/shadcn/menubar/sub/component.rb +43 -0
- data/app/components/shadcn/menubar/sub_content/component.rb +31 -0
- data/app/components/shadcn/menubar/sub_trigger/component.rb +31 -0
- data/app/components/shadcn/menubar/trigger/component.rb +41 -0
- data/app/components/shadcn/menubar.rb +47 -0
- data/app/components/shadcn/message/component.rb +33 -0
- data/app/components/shadcn/message/preview.rb +16 -0
- data/app/components/shadcn/message/previews/actions.html.erb +51 -0
- data/app/components/shadcn/message/previews/default.html.erb +35 -0
- data/app/components/shadcn/message.rb +35 -0
- data/app/components/shadcn/message_scroller/button/component.rb +78 -0
- data/app/components/shadcn/message_scroller/component.rb +59 -0
- data/app/components/shadcn/message_scroller/content/component.rb +50 -0
- data/app/components/shadcn/message_scroller/item/component.rb +45 -0
- data/app/components/shadcn/message_scroller/preview.rb +11 -0
- data/app/components/shadcn/message_scroller/previews/default.html.erb +72 -0
- data/app/components/shadcn/message_scroller/viewport/component.rb +41 -0
- data/app/components/shadcn/mode_switcher/component.rb +67 -0
- data/app/components/shadcn/mode_switcher/preview.rb +11 -0
- data/app/components/shadcn/mode_switcher/previews/default.html.erb +5 -0
- data/app/components/shadcn/mode_toggle/component.rb +75 -0
- data/app/components/shadcn/mode_toggle/preview.rb +11 -0
- data/app/components/shadcn/mode_toggle/previews/default.html.erb +20 -0
- data/app/components/shadcn/native_select/component.rb +59 -0
- data/app/components/shadcn/native_select/preview.rb +16 -0
- data/app/components/shadcn/native_select/previews/default.html.erb +9 -0
- data/app/components/shadcn/native_select/previews/groups.html.erb +26 -0
- data/app/components/shadcn/native_select.rb +16 -0
- data/app/components/shadcn/navigation_menu/component.rb +51 -0
- data/app/components/shadcn/navigation_menu/content/component.rb +58 -0
- data/app/components/shadcn/navigation_menu/preview.rb +11 -0
- data/app/components/shadcn/navigation_menu/previews/default.html.erb +49 -0
- data/app/components/shadcn/navigation_menu/trigger/component.rb +59 -0
- data/app/components/shadcn/navigation_menu.rb +28 -0
- data/app/components/shadcn/pagination/component.rb +19 -0
- data/app/components/shadcn/pagination/ellipsis/component.rb +28 -0
- data/app/components/shadcn/pagination/link/component.rb +37 -0
- data/app/components/shadcn/pagination/next/component.rb +31 -0
- data/app/components/shadcn/pagination/preview.rb +16 -0
- data/app/components/shadcn/pagination/previews/default.html.erb +18 -0
- data/app/components/shadcn/pagination/previews/simple.html.erb +39 -0
- data/app/components/shadcn/pagination/previous/component.rb +31 -0
- data/app/components/shadcn/pagination.rb +14 -0
- data/app/components/shadcn/parts.rb +38 -0
- data/app/components/shadcn/popover/anchor/component.rb +17 -0
- data/app/components/shadcn/popover/component.rb +42 -0
- data/app/components/shadcn/popover/content/component.rb +40 -0
- data/app/components/shadcn/popover/preview.rb +28 -0
- data/app/components/shadcn/popover/previews/align.html.erb +13 -0
- data/app/components/shadcn/popover/previews/default.html.erb +9 -0
- data/app/components/shadcn/popover/previews/inside_containing_block.html.erb +41 -0
- data/app/components/shadcn/popover/previews/inside_stacking_context.html.erb +30 -0
- data/app/components/shadcn/popover/trigger/component.rb +24 -0
- data/app/components/shadcn/popover.rb +16 -0
- data/app/components/shadcn/progress/component.rb +62 -0
- data/app/components/shadcn/progress/preview.rb +11 -0
- data/app/components/shadcn/progress/previews/default.html.erb +5 -0
- data/app/components/shadcn/radio_group/component.rb +51 -0
- data/app/components/shadcn/radio_group/item/component.rb +74 -0
- data/app/components/shadcn/radio_group/preview.rb +16 -0
- data/app/components/shadcn/radio_group/previews/default.html.erb +15 -0
- data/app/components/shadcn/radio_group/previews/states.html.erb +41 -0
- data/app/components/shadcn/resizable/component.rb +59 -0
- data/app/components/shadcn/resizable/handle/component.rb +70 -0
- data/app/components/shadcn/resizable/panel/component.rb +53 -0
- data/app/components/shadcn/resizable/preview.rb +23 -0
- data/app/components/shadcn/resizable/previews/default.html.erb +25 -0
- data/app/components/shadcn/resizable/previews/vertical.html.erb +19 -0
- data/app/components/shadcn/resizable/previews/with_handle.html.erb +17 -0
- data/app/components/shadcn/scroll_area/component.rb +84 -0
- data/app/components/shadcn/scroll_area/preview.rb +11 -0
- data/app/components/shadcn/scroll_area/previews/default.html.erb +36 -0
- data/app/components/shadcn/scroll_area/scrollbar/component.rb +72 -0
- data/app/components/shadcn/scroll_area/viewport/component.rb +70 -0
- data/app/components/shadcn/scroll_area.rb +14 -0
- data/app/components/shadcn/select/component.rb +67 -0
- data/app/components/shadcn/select/content/component.rb +157 -0
- data/app/components/shadcn/select/empty/component.rb +38 -0
- data/app/components/shadcn/select/group/component.rb +16 -0
- data/app/components/shadcn/select/item/component.rb +71 -0
- data/app/components/shadcn/select/list/component.rb +41 -0
- data/app/components/shadcn/select/preview.rb +24 -0
- data/app/components/shadcn/select/previews/default.html.erb +17 -0
- data/app/components/shadcn/select/previews/scrollable.html.erb +19 -0
- data/app/components/shadcn/select/previews/searchable.html.erb +18 -0
- data/app/components/shadcn/select/previews/states.html.erb +25 -0
- data/app/components/shadcn/select/search/component.rb +54 -0
- data/app/components/shadcn/select/trigger/component.rb +78 -0
- data/app/components/shadcn/select/value/component.rb +28 -0
- data/app/components/shadcn/select.rb +15 -0
- data/app/components/shadcn/separator/component.rb +41 -0
- data/app/components/shadcn/separator/preview.rb +11 -0
- data/app/components/shadcn/separator/previews/default.html.erb +9 -0
- data/app/components/shadcn/sheet/component.rb +16 -0
- data/app/components/shadcn/sheet/content/component.rb +98 -0
- data/app/components/shadcn/sheet/header/component.rb +23 -0
- data/app/components/shadcn/sheet/preview.rb +16 -0
- data/app/components/shadcn/sheet/previews/default.html.erb +16 -0
- data/app/components/shadcn/sheet/previews/no_close_button.html.erb +14 -0
- data/app/components/shadcn/sheet.rb +22 -0
- data/app/components/shadcn/sidebar/component.rb +232 -0
- data/app/components/shadcn/sidebar/input/component.rb +25 -0
- data/app/components/shadcn/sidebar/menu_action/component.rb +58 -0
- data/app/components/shadcn/sidebar/menu_button/component.rb +100 -0
- data/app/components/shadcn/sidebar/menu_skeleton/component.rb +51 -0
- data/app/components/shadcn/sidebar/menu_sub_button/component.rb +60 -0
- data/app/components/shadcn/sidebar/preview.rb +22 -0
- data/app/components/shadcn/sidebar/previews/default.html.erb +189 -0
- data/app/components/shadcn/sidebar/previews/floating.html.erb +39 -0
- data/app/components/shadcn/sidebar/previews/inset.html.erb +48 -0
- data/app/components/shadcn/sidebar/provider/component.rb +53 -0
- data/app/components/shadcn/sidebar/rail/component.rb +43 -0
- data/app/components/shadcn/sidebar/separator/component.rb +23 -0
- data/app/components/shadcn/sidebar/trigger/component.rb +39 -0
- data/app/components/shadcn/sidebar.rb +98 -0
- data/app/components/shadcn/skeleton/component.rb +14 -0
- data/app/components/shadcn/skeleton/preview.rb +11 -0
- data/app/components/shadcn/skeleton/previews/default.html.erb +7 -0
- data/app/components/shadcn/slider/component.rb +145 -0
- data/app/components/shadcn/slider/preview.rb +16 -0
- data/app/components/shadcn/slider/previews/default.html.erb +17 -0
- data/app/components/shadcn/slider/previews/vertical.html.erb +7 -0
- data/app/components/shadcn/slider/range/component.rb +51 -0
- data/app/components/shadcn/slider/thumb/component.rb +58 -0
- data/app/components/shadcn/slider/track/component.rb +32 -0
- data/app/components/shadcn/spinner/component.rb +21 -0
- data/app/components/shadcn/spinner/preview.rb +11 -0
- data/app/components/shadcn/spinner/previews/default.html.erb +7 -0
- data/app/components/shadcn/switch/component.rb +104 -0
- data/app/components/shadcn/switch/preview.rb +11 -0
- data/app/components/shadcn/switch/previews/default.html.erb +24 -0
- data/app/components/shadcn/table/component.rb +26 -0
- data/app/components/shadcn/table/preview.rb +16 -0
- data/app/components/shadcn/table/previews/default.html.erb +19 -0
- data/app/components/shadcn/table/previews/footer.html.erb +25 -0
- data/app/components/shadcn/table.rb +33 -0
- data/app/components/shadcn/tabs/component.rb +40 -0
- data/app/components/shadcn/tabs/content/component.rb +34 -0
- data/app/components/shadcn/tabs/list/component.rb +51 -0
- data/app/components/shadcn/tabs/preview.rb +16 -0
- data/app/components/shadcn/tabs/previews/default.html.erb +21 -0
- data/app/components/shadcn/tabs/previews/line.html.erb +23 -0
- data/app/components/shadcn/tabs/trigger/component.rb +66 -0
- data/app/components/shadcn/textarea/component.rb +24 -0
- data/app/components/shadcn/textarea/preview.rb +16 -0
- data/app/components/shadcn/textarea/previews/default.html.erb +3 -0
- data/app/components/shadcn/textarea/previews/states.html.erb +18 -0
- data/app/components/shadcn/theme_selector/component.rb +88 -0
- data/app/components/shadcn/theme_selector/preview.rb +17 -0
- data/app/components/shadcn/theme_selector/previews/all_themes.html.erb +20 -0
- data/app/components/shadcn/theme_selector/previews/default.html.erb +18 -0
- data/app/components/shadcn/toaster/component.rb +139 -0
- data/app/components/shadcn/toaster/preview.rb +16 -0
- data/app/components/shadcn/toaster/previews/default.html.erb +19 -0
- data/app/components/shadcn/toaster/previews/from_the_server.html.erb +15 -0
- data/app/components/shadcn/toaster/toast/component.rb +134 -0
- data/app/components/shadcn/toggle/component.rb +81 -0
- data/app/components/shadcn/toggle/preview.rb +11 -0
- data/app/components/shadcn/toggle/previews/default.html.erb +6 -0
- data/app/components/shadcn/toggle_group/component.rb +62 -0
- data/app/components/shadcn/toggle_group/item/component.rb +59 -0
- data/app/components/shadcn/toggle_group/preview.rb +16 -0
- data/app/components/shadcn/toggle_group/previews/default.html.erb +12 -0
- data/app/components/shadcn/toggle_group/previews/sizes.html.erb +37 -0
- data/app/components/shadcn/tooltip/component.rb +38 -0
- data/app/components/shadcn/tooltip/content/component.rb +62 -0
- data/app/components/shadcn/tooltip/preview.rb +21 -0
- data/app/components/shadcn/tooltip/previews/default.html.erb +4 -0
- data/app/components/shadcn/tooltip/previews/sides.html.erb +19 -0
- data/app/components/shadcn/tooltip/previews/with_keyboard_shortcut.html.erb +11 -0
- data/app/components/shadcn/tooltip/provider/component.rb +28 -0
- data/app/components/shadcn/tooltip/trigger/component.rb +25 -0
- data/app/helpers/shadcn_view_component/form_helper.rb +25 -0
- data/app/helpers/shadcn_view_component/theme_helper.rb +61 -0
- data/app/javascript/shadcn/animation.js +180 -0
- data/app/javascript/shadcn/command_score.js +110 -0
- data/app/javascript/shadcn/controllers/accordion_controller.js +127 -0
- data/app/javascript/shadcn/controllers/avatar_controller.js +32 -0
- data/app/javascript/shadcn/controllers/calendar_controller.js +633 -0
- data/app/javascript/shadcn/controllers/carousel_controller.js +139 -0
- data/app/javascript/shadcn/controllers/chart_controller.js +110 -0
- data/app/javascript/shadcn/controllers/checkbox_controller.js +61 -0
- data/app/javascript/shadcn/controllers/collapsible_controller.js +55 -0
- data/app/javascript/shadcn/controllers/combobox_controller.js +360 -0
- data/app/javascript/shadcn/controllers/command_controller.js +199 -0
- data/app/javascript/shadcn/controllers/dialog_controller.js +154 -0
- data/app/javascript/shadcn/controllers/drawer_controller.js +265 -0
- data/app/javascript/shadcn/controllers/dropdown_menu_controller.js +259 -0
- data/app/javascript/shadcn/controllers/hover_card_controller.js +85 -0
- data/app/javascript/shadcn/controllers/input_group_controller.js +17 -0
- data/app/javascript/shadcn/controllers/input_otp_controller.js +80 -0
- data/app/javascript/shadcn/controllers/menubar_controller.js +151 -0
- data/app/javascript/shadcn/controllers/message_scroller_controller.js +456 -0
- data/app/javascript/shadcn/controllers/navigation_menu_controller.js +211 -0
- data/app/javascript/shadcn/controllers/popover_controller.js +66 -0
- data/app/javascript/shadcn/controllers/radio_group_controller.js +69 -0
- data/app/javascript/shadcn/controllers/resizable_controller.js +167 -0
- data/app/javascript/shadcn/controllers/scroll_area_controller.js +253 -0
- data/app/javascript/shadcn/controllers/select_controller.js +320 -0
- data/app/javascript/shadcn/controllers/sidebar_controller.js +273 -0
- data/app/javascript/shadcn/controllers/slider_controller.js +230 -0
- data/app/javascript/shadcn/controllers/switch_controller.js +39 -0
- data/app/javascript/shadcn/controllers/tabs_controller.js +81 -0
- data/app/javascript/shadcn/controllers/theme_controller.js +67 -0
- data/app/javascript/shadcn/controllers/toaster_controller.js +281 -0
- data/app/javascript/shadcn/controllers/toggle_controller.js +24 -0
- data/app/javascript/shadcn/controllers/toggle_group_controller.js +73 -0
- data/app/javascript/shadcn/controllers/tooltip_controller.js +58 -0
- data/app/javascript/shadcn/direction.js +41 -0
- data/app/javascript/shadcn/dismiss.js +73 -0
- data/app/javascript/shadcn/floating.js +207 -0
- data/app/javascript/shadcn/focus.js +86 -0
- data/app/javascript/shadcn/id.js +14 -0
- data/app/javascript/shadcn/index.js +110 -0
- data/app/javascript/shadcn/popper.js +262 -0
- data/app/javascript/shadcn/scroll_geometry.js +267 -0
- data/app/javascript/shadcn/theme.js +161 -0
- data/app/javascript/shadcn/top_layer.js +59 -0
- data/app/javascript/shadcn/typeahead.js +99 -0
- data/config/importmap.rb +6 -0
- data/config/locales/en.yml +62 -0
- data/lib/generators/shadcn_view_component/install/install_generator.rb +149 -0
- data/lib/shadcn_view_component/engine.rb +73 -0
- data/lib/shadcn_view_component/form_builder.rb +264 -0
- data/lib/shadcn_view_component/icon_registry.rb +66 -0
- data/lib/shadcn_view_component/icons.rb +32 -0
- data/lib/shadcn_view_component/themes.rb +1065 -0
- data/lib/shadcn_view_component/version.rb +3 -0
- data/lib/shadcn_view_component.rb +82 -0
- data/lib/tasks/icons.rake +53 -0
- data/lib/tasks/themes.rake +171 -0
- metadata +608 -0
data/README.md
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
# shadcn_view_component
|
|
2
|
+
|
|
3
|
+
[shadcn/ui](https://ui.shadcn.com) ported to Rails [ViewComponent](https://viewcomponent.org),
|
|
4
|
+
1:1 — the same part names, the same variants, the same Tailwind classes and
|
|
5
|
+
`data-slot` attributes. Radix UI's behaviour is reimplemented with Stimulus, so
|
|
6
|
+
there is no React and no npm dependency.
|
|
7
|
+
|
|
8
|
+
Two specs hold the port to upstream. `spec/parity_spec.rb` reads the TSX
|
|
9
|
+
vendored in `vendor/shadcn/ui` and fails if a class React emits is missing from
|
|
10
|
+
the Ruby; `spec/snapshot_spec.rb` diffs the rendered HTML of every preview
|
|
11
|
+
against a committed golden file. See
|
|
12
|
+
[what the specs prove](#what-is-and-is-not-verified) for the honest version.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
# Gemfile
|
|
18
|
+
gem "shadcn_view_component"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
bin/rails generate shadcn_view_component:install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The generator adds the imports to your Tailwind entrypoint and the Stimulus
|
|
26
|
+
registration to `application.js`. Those three CSS lines are the reason it
|
|
27
|
+
exists: each one names a path into the gem, and that path differs between a
|
|
28
|
+
system gem, `bundle config set path`, and a `path:` or `git:` source.
|
|
29
|
+
|
|
30
|
+
**They are filesystem paths, not asset-pipeline names.** `tailwindcss-rails`
|
|
31
|
+
runs the Tailwind CLI with `-i` and `-o` and no load path, so the CLI resolves
|
|
32
|
+
a bare `@import "shadcn.css"` the way Node would — beside the file, then in
|
|
33
|
+
`node_modules` — and a Rails app has neither. It stops the build with
|
|
34
|
+
`Can't resolve 'shadcn.css'`.
|
|
35
|
+
|
|
36
|
+
Doing it by hand, with `PATH` from `bundle show shadcn_view_component`:
|
|
37
|
+
|
|
38
|
+
```css
|
|
39
|
+
/* app/assets/tailwind/application.css */
|
|
40
|
+
@import "tailwindcss";
|
|
41
|
+
@import "PATH/app/assets/stylesheets/shadcn.css";
|
|
42
|
+
@import "PATH/app/assets/stylesheets/shadcn-themes.css"; /* optional: the swappable palettes */
|
|
43
|
+
|
|
44
|
+
@source "PATH/app/components";
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`shadcn-themes.css` has to come after `shadcn.css`: `.theme-*` and `:root` have
|
|
48
|
+
the same specificity, so source order is what decides.
|
|
49
|
+
|
|
50
|
+
**Prefer a relative path where you can.** An absolute one is correct on the
|
|
51
|
+
machine that wrote it and wrong on every other, and the CSS is built on all of
|
|
52
|
+
them. With `bundle config set path vendor/bundle` — what CI and most containers
|
|
53
|
+
do — the gem lives inside the application and the three lines can be written
|
|
54
|
+
relative to the entrypoint, which then holds everywhere. That is what the
|
|
55
|
+
generator writes when it can.
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
// app/javascript/controllers/index.js
|
|
59
|
+
import { registerShadcnControllers } from "shadcn"
|
|
60
|
+
registerShadcnControllers(application)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**That file, and not `app/javascript/application.js`.** `controllers/index.js`
|
|
64
|
+
is where importmap-rails puts `import { application } from
|
|
65
|
+
"controllers/application"`, so the registration has an instance to attach to.
|
|
66
|
+
`application.js` imports `"controllers"` for the side effect and defines no
|
|
67
|
+
such binding: register there and the browser says `ReferenceError: application
|
|
68
|
+
is not defined`, nothing is registered, and every dialog, select and menu is
|
|
69
|
+
inert — with the CSS working, so it looks installed. Starting a second
|
|
70
|
+
`Application.start()` instead is worse in a subtler way: two Stimulus instances
|
|
71
|
+
then fight over every element they both claim.
|
|
72
|
+
|
|
73
|
+
If your app has no Stimulus yet, start one and pass it that same way.
|
|
74
|
+
|
|
75
|
+
The engine contributes its own importmap pins, so `shadcn` resolves with no
|
|
76
|
+
further configuration when the host app uses `importmap-rails`.
|
|
77
|
+
|
|
78
|
+
### Configuration
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
# config/application.rb
|
|
82
|
+
config.shadcn_view_component.cache_size = 50_000
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
tailwind-merge keeps an LRU of merged class strings. Its own default is 500,
|
|
86
|
+
which a page built from these components can exhaust — and a Rails render is the
|
|
87
|
+
worst case for an LRU, since it cycles the same keys every request, so once the
|
|
88
|
+
working set exceeds the cache the hit rate collapses and stays collapsed. This
|
|
89
|
+
gem defaults to 10,000; raise it if you generate many distinct computed classes
|
|
90
|
+
(`w-[#{percent}%]` down a long table, say).
|
|
91
|
+
|
|
92
|
+
## Usage
|
|
93
|
+
|
|
94
|
+
Every React part is one component class, under `Shadcn::`:
|
|
95
|
+
|
|
96
|
+
| React | Ruby |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `<Button variant="outline" size="sm">` | `Shadcn::Button::Component.new(variant: :outline, size: :sm)` |
|
|
99
|
+
| `<CardHeader>` | `Shadcn::Card::Header::Component` |
|
|
100
|
+
| `<SelectItem value="apple">` | `Shadcn::Select::Item::Component.new(value: "apple")` |
|
|
101
|
+
|
|
102
|
+
Parents also expose slots, so the common case stays short:
|
|
103
|
+
|
|
104
|
+
```erb
|
|
105
|
+
<%= render(Shadcn::Card::Component.new(class: "w-full max-w-sm")) do |card| %>
|
|
106
|
+
<% card.with_header do |header| %>
|
|
107
|
+
<% header.with_title { "Login to your account" } %>
|
|
108
|
+
<% header.with_description { "Enter your email below to login." } %>
|
|
109
|
+
<% end %>
|
|
110
|
+
<% card.with_card_content do %>
|
|
111
|
+
<%= render(Shadcn::Input::Component.new(type: "email", placeholder: "m@example.com")) %>
|
|
112
|
+
<% end %>
|
|
113
|
+
<% card.with_footer do %>
|
|
114
|
+
<%= render(Shadcn::Button::Component.new(class: "w-full")) { "Login" } %>
|
|
115
|
+
<% end %>
|
|
116
|
+
<% end %>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The explicit form is always available, and is what you want when order matters
|
|
120
|
+
or when parts are interleaved with labels and separators:
|
|
121
|
+
|
|
122
|
+
```erb
|
|
123
|
+
<%= render(Shadcn::Card::Component.new) do %>
|
|
124
|
+
<%= render(Shadcn::Card::Header::Component.new) do %>…<% end %>
|
|
125
|
+
<% end %>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Slot content is rendered *before* block content, so don't mix the two inside one
|
|
129
|
+
parent unless you mean that ordering.
|
|
130
|
+
|
|
131
|
+
User-visible strings go through I18n under `shadcn_view_component.*`, with
|
|
132
|
+
shadcn's English as the default — override any key in your own locale files.
|
|
133
|
+
|
|
134
|
+
Anything you pass beyond the documented arguments lands on the element, exactly
|
|
135
|
+
like React's `{...props}`, and wins over what the component set itself:
|
|
136
|
+
|
|
137
|
+
```erb
|
|
138
|
+
<%= render(Shadcn::Button::Component.new(type: "submit", disabled: true, data: { turbo: false })) { "Save" } %>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`as:` is the port of shadcn's `asChild`:
|
|
142
|
+
|
|
143
|
+
```erb
|
|
144
|
+
<%= render(Shadcn::Button::Component.new(as: :a, href: "/login", variant: :link)) { "Log in" } %>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Rails forms
|
|
148
|
+
|
|
149
|
+
`shadcn_form_with` is `form_with` with a builder that wires the `Field` family
|
|
150
|
+
to a model. Ids and names come from Rails; the error text, `aria-invalid`,
|
|
151
|
+
`aria-describedby` and `Field`'s `data-invalid` come from `ActiveModel::Errors`.
|
|
152
|
+
|
|
153
|
+
```erb
|
|
154
|
+
<%= shadcn_form_with model: @user do |f| %>
|
|
155
|
+
<%= f.shadcn_input_field :email, label: "Email",
|
|
156
|
+
description: "We never share it.",
|
|
157
|
+
type: "email" %>
|
|
158
|
+
|
|
159
|
+
<%= f.shadcn_field :plan, label: "Plan" do %>
|
|
160
|
+
<%= f.shadcn_select :plan, [["Free", "free"], ["Pro", "pro"]] %>
|
|
161
|
+
<% end %>
|
|
162
|
+
|
|
163
|
+
<%= f.shadcn_submit "Create account" %>
|
|
164
|
+
<% end %>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`shadcn_field` is the wrapper — label, control, description, errors — and takes
|
|
168
|
+
any control in its block. `shadcn_<control>_field` is the shorthand for the
|
|
169
|
+
common case of exactly one.
|
|
170
|
+
|
|
171
|
+
| Control | Notes |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `shadcn_input` | |
|
|
174
|
+
| `shadcn_textarea` | |
|
|
175
|
+
| `shadcn_native_select` | a real `<select>`: browser validation, autofill, native keyboard behaviour |
|
|
176
|
+
| `shadcn_select` | the styled listbox; submits through a hidden input, so `required` will **not** stop the form |
|
|
177
|
+
| `shadcn_checkbox`, `shadcn_switch` | boolean attributes |
|
|
178
|
+
| `shadcn_radio_group` | each option labelled and wired by id |
|
|
179
|
+
| `shadcn_submit` | |
|
|
180
|
+
|
|
181
|
+
Prefer `shadcn_native_select` unless you specifically want the styled listbox —
|
|
182
|
+
it is the one control here that a browser understands.
|
|
183
|
+
|
|
184
|
+
`form_with(..., builder: ShadcnViewComponent::FormBuilder)` does the same thing
|
|
185
|
+
if you would rather be explicit, and works with `fields_for` too.
|
|
186
|
+
|
|
187
|
+
### A select you can filter
|
|
188
|
+
|
|
189
|
+
`searchable: true` puts a search field at the top of the open panel and narrows
|
|
190
|
+
the options as you type — case-insensitively, on a substring, so `err` finds
|
|
191
|
+
*Blueberry*. It works on the component and through the form builder:
|
|
192
|
+
|
|
193
|
+
```erb
|
|
194
|
+
<%= render(Shadcn::Select::Component.new(name: "country", searchable: true)) do |s| %>
|
|
195
|
+
<% s.with_trigger { |t| t.with_value(placeholder: "Select a country") } %>
|
|
196
|
+
<% s.with_select_content do %>
|
|
197
|
+
<%= render(Shadcn::Select::Item::Component.new(value: "it")) { "Italy" } %>
|
|
198
|
+
<% end %>
|
|
199
|
+
<% end %>
|
|
200
|
+
|
|
201
|
+
<%= f.shadcn_select :country, choices, searchable: true %>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Two things to know before reaching for it.
|
|
205
|
+
|
|
206
|
+
**The filter is client-side**, over the options already on the page. For a list
|
|
207
|
+
long enough that you would not render it all, listen for `input` on
|
|
208
|
+
`[data-slot=select-input-wrapper] input` and swap the options through a Turbo
|
|
209
|
+
Frame — the gem takes no position on that and ships no server mode.
|
|
210
|
+
|
|
211
|
+
**This one is not a port.** Every other component here reproduces a shadcn
|
|
212
|
+
component; no Radix-based shadcn select has a filter, so this one takes its
|
|
213
|
+
shape from the React Aria variant and is otherwise the gem's own. What that
|
|
214
|
+
means for you: its look is not guaranteed to match a future upstream, and
|
|
215
|
+
[`.claude/docs/decisions/01-architecture.md`](.claude/docs/decisions/01-architecture.md)
|
|
216
|
+
records where it deliberately differs.
|
|
217
|
+
|
|
218
|
+
## How the mapping works
|
|
219
|
+
|
|
220
|
+
| shadcn (React) | this gem |
|
|
221
|
+
|---|---|
|
|
222
|
+
| `cva(base, { variants, defaultVariants })` | `ViewComponentContrib::StyleVariants` — the `style { base {} variants {} defaults {} }` DSL |
|
|
223
|
+
| `cn(...)` (clsx + tailwind-merge) | the `tailwind_merge` gem, wired in as the style postprocessor, so caller classes win conflicts |
|
|
224
|
+
| `data-slot="card-header"` | `slot_name :"card-header"` |
|
|
225
|
+
| `{...props}` | `**attributes`, splatted through Rails' tag builder |
|
|
226
|
+
| `asChild` | `as:` |
|
|
227
|
+
| Radix primitives | Stimulus controllers under `shadcn--*`, keeping the same `data-state`, `aria-*` and `--radix-*` custom properties in step with what the user does |
|
|
228
|
+
| `lucide-react` icons | `Shadcn::Icon::Component`, with the lucide SVGs inlined |
|
|
229
|
+
|
|
230
|
+
**The ARIA is the component's, not the controller's.** Whatever a part needs to
|
|
231
|
+
be what it is — its `role`, its `aria-haspopup`, a `tabindex`, the
|
|
232
|
+
`aria-expanded` it starts closed with — is rendered by the Ruby, so the markup
|
|
233
|
+
is right before any JavaScript runs and a `turbo:morph` cannot undo it. The
|
|
234
|
+
controllers only write what changes as the user acts — `aria-expanded` on open,
|
|
235
|
+
`aria-activedescendant` as the cursor moves, `aria-controls` once it knows
|
|
236
|
+
which element to point at. Four of them used to re-set the static half on
|
|
237
|
+
connect, and that was deliberately removed (commit `4e88573`): two places to
|
|
238
|
+
write one attribute is two places to drift.
|
|
239
|
+
|
|
240
|
+
The consequence, if you write your own markup: **`data-controller` and a
|
|
241
|
+
`data-shadcn--*-target` are not enough.** Hang them on a bare `<div>` and you
|
|
242
|
+
get a div — no role, no `aria-haspopup`, nothing for a screen reader to
|
|
243
|
+
announce, and no error to tell you. Render the component, or copy every
|
|
244
|
+
attribute it emits — `bin/console` prints them:
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
render Shadcn::Select::Trigger::Component.new
|
|
248
|
+
# => <button data-slot="select-trigger" type="button" role="combobox"
|
|
249
|
+
# aria-expanded="false" aria-autocomplete="none" …>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
21 lucide icons are bundled — exactly the ones the ported components render,
|
|
253
|
+
out of lucide's ~1,500, and a spec fails if that stops being exactly true in
|
|
254
|
+
either direction. Their drawings are not typed into Ruby: they are the files lucide publishes,
|
|
255
|
+
vendored in the repository under `vendor/lucide/icons`, and `rake icons:build`
|
|
256
|
+
turns them into the registry the component reads. Only that registry ships in
|
|
257
|
+
the gem — the SVGs are a build-time source, like the upstream TSX.
|
|
258
|
+
|
|
259
|
+
**To add your own**, put SVG files in a directory and point the registry at it
|
|
260
|
+
from an initializer — that is where `cache_size` lives too, and the reason both
|
|
261
|
+
go through `ShadcnViewComponent::IconRegistry` is that nothing autoloadable
|
|
262
|
+
resolves there, `Shadcn::` included:
|
|
263
|
+
|
|
264
|
+
```ruby
|
|
265
|
+
# config/initializers/shadcn_view_component.rb
|
|
266
|
+
ShadcnViewComponent::IconRegistry.load_directory(Rails.root.join("app/assets/icons"))
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
The file's basename is the icon's name, and only what the `<svg>` element
|
|
270
|
+
contains is kept — the outer element is the component's, so lucide's own
|
|
271
|
+
attributes and yours never end up arguing. No asset pipeline is involved: the
|
|
272
|
+
files are read once, by you, when you call this. A single drawing can still be
|
|
273
|
+
registered by hand:
|
|
274
|
+
|
|
275
|
+
```ruby
|
|
276
|
+
# config/initializers/shadcn_view_component.rb
|
|
277
|
+
ShadcnViewComponent::IconRegistry.register("star", %(<path d="M12 2 15 9l7 .5-5 4 1 7-6-3z"/>))
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```erb
|
|
281
|
+
<%= render Shadcn::Icon::Component.new("star", class: "size-4") %>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Registering a name the gem already bundles replaces it — `register("check", …)`
|
|
285
|
+
changes the tick in every checkbox, select and dropdown item — so what ships is
|
|
286
|
+
a set of defaults, not a fixed set.
|
|
287
|
+
|
|
288
|
+
`Shadcn::Icon.register` / `.registered` delegate to the same registry and read
|
|
289
|
+
more naturally from a view or another component — anywhere autoloading has
|
|
290
|
+
already run, which is everywhere except an initializer.
|
|
291
|
+
|
|
292
|
+
An unknown name raises where `Rails.env.local?` is true — development and
|
|
293
|
+
test, where a typo can still be fixed — and renders nothing everywhere else,
|
|
294
|
+
staging included: the gem cannot know every icon a host will ever pass it,
|
|
295
|
+
and a missing one is not worth a 500.
|
|
296
|
+
|
|
297
|
+
Components live in sidecar directories following the
|
|
298
|
+
[Evil Martians layout](https://evilmartians.com/chronicles/viewcomponent-in-the-wild-building-modern-rails-frontends):
|
|
299
|
+
`app/components/shadcn/<family>/[<part>/]{component.rb,preview.rb,previews/*.html.erb}`.
|
|
300
|
+
|
|
301
|
+
Everything is namespaced under `Shadcn::` rather than sitting at the top level,
|
|
302
|
+
because names like `Card`, `Table`, `Field` and `Select` would otherwise collide
|
|
303
|
+
with a host application's own models.
|
|
304
|
+
|
|
305
|
+
## Theming
|
|
306
|
+
|
|
307
|
+
Theming works exactly as shadcn documents it: semantic CSS variables under
|
|
308
|
+
`:root` and `.dark`, mapped into Tailwind utilities by `@theme inline`. Override
|
|
309
|
+
`--primary` and every `bg-primary` in the app follows. The full token table is
|
|
310
|
+
in [the shadcn theming docs](https://ui.shadcn.com/docs/theming); this gem ships
|
|
311
|
+
the same tokens and the same `--radius` scale.
|
|
312
|
+
|
|
313
|
+
On top of that there are two independent switchable axes, ported from what runs
|
|
314
|
+
on ui.shadcn.com:
|
|
315
|
+
|
|
316
|
+
| Axis | Values | Applied as |
|
|
317
|
+
|---|---|---|
|
|
318
|
+
| mode | `light`, `dark`, `system` | `.dark` on `<html>` |
|
|
319
|
+
| theme | one of 24 palettes | `theme-<name>` on `<body>` |
|
|
320
|
+
|
|
321
|
+
### Wiring it up
|
|
322
|
+
|
|
323
|
+
```erb
|
|
324
|
+
<%# app/views/layouts/application.html.erb %>
|
|
325
|
+
<html>
|
|
326
|
+
<head>
|
|
327
|
+
<%= shadcn_theme_script_tag %>
|
|
328
|
+
</head>
|
|
329
|
+
<body class="<%= shadcn_theme_class %>">
|
|
330
|
+
<%= render(Shadcn::ModeToggle::Component.new) %>
|
|
331
|
+
<%= render(Shadcn::ThemeSelector::Component.new(value: shadcn_theme_name)) %>
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
`shadcn_theme_script_tag` is the piece that matters: it runs before the first
|
|
335
|
+
paint, reads the stored preference and sets `.dark` and `data-shadcn-theme` on
|
|
336
|
+
`<html>` — which is why there is no flash of the wrong palette. It is the
|
|
337
|
+
equivalent of what `next-themes` injects.
|
|
338
|
+
|
|
339
|
+
The preference is stored in `localStorage` (as upstream) and mirrored into a
|
|
340
|
+
cookie, so `shadcn_theme_class` and `shadcn_mode` can render the right thing
|
|
341
|
+
server-side on the very first byte.
|
|
342
|
+
|
|
343
|
+
### The switchers
|
|
344
|
+
|
|
345
|
+
| Component | Upstream | What it is |
|
|
346
|
+
|---|---|---|
|
|
347
|
+
| `Shadcn::ModeToggle::Component` | `examples/mode-toggle.tsx` | The documented dropdown: Light / Dark / System |
|
|
348
|
+
| `Shadcn::ModeSwitcher::Component` | `components/mode-switcher.tsx` | The single button in shadcn's own header, straight light↔dark |
|
|
349
|
+
| `Shadcn::ThemeSelector::Component` | `components/theme-selector.tsx` | A select over the palettes |
|
|
350
|
+
|
|
351
|
+
Driving it yourself is a matter of one Stimulus action inside a
|
|
352
|
+
`shadcn--theme` controller:
|
|
353
|
+
|
|
354
|
+
```erb
|
|
355
|
+
<div data-controller="shadcn--theme">
|
|
356
|
+
<button data-action="shadcn--theme#toggle">Flip the mode</button>
|
|
357
|
+
<button data-mode="dark" data-action="click->shadcn--theme#setMode">Dark</button>
|
|
358
|
+
<button data-value="zinc" data-action="click->shadcn--theme#setTheme">Zinc</button>
|
|
359
|
+
</div>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Or from your own JavaScript:
|
|
363
|
+
|
|
364
|
+
```js
|
|
365
|
+
import { setMode, setTheme, resolvedMode } from "shadcn/theme"
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### The palettes
|
|
369
|
+
|
|
370
|
+
Seven base greys — neutral, stone, zinc, mauve, olive, mist, taupe — and
|
|
371
|
+
seventeen accents: amber, blue, cyan, emerald, fuchsia, green, indigo, lime,
|
|
372
|
+
orange, pink, purple, red, rose, sky, teal, violet, yellow.
|
|
373
|
+
|
|
374
|
+
The base greys carry a complete token set. The accents are overlays: they
|
|
375
|
+
redefine `--primary` and the chart colours and let the rest fall through to
|
|
376
|
+
`:root`, which is why dropping one onto `<body>` recolours an app without
|
|
377
|
+
restating the neutrals.
|
|
378
|
+
|
|
379
|
+
Palettes also scope, so you can theme one region of a page:
|
|
380
|
+
|
|
381
|
+
```erb
|
|
382
|
+
<div class="theme-blue">…</div>
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
`ShadcnViewComponent::Themes` is the registry behind all of this
|
|
386
|
+
(`ALL`, `BASE_COLORS`, `ACCENTS`, `find`). Both the registry and
|
|
387
|
+
`shadcn-themes.css` are generated from `vendor/shadcn/themes.json` — run
|
|
388
|
+
`rake themes:build` after refreshing it.
|
|
389
|
+
|
|
390
|
+
### A few rules resist an ordinary `!important`
|
|
391
|
+
|
|
392
|
+
`[data-slot][hidden]`, `[data-slot][data-exiting]` and the two
|
|
393
|
+
`animate-accordion-*` reduced-motion overrides in `shadcn.css` are
|
|
394
|
+
`!important` inside a cascade layer, which beats an `!important` of your own
|
|
395
|
+
at any specificity. An inline `style` attribute gets past it, and so does a
|
|
396
|
+
`@layer` declared earlier than this stylesheet in your document.
|
|
397
|
+
|
|
398
|
+
## Components
|
|
399
|
+
|
|
400
|
+
**Theming** — mode-toggle, mode-switcher, theme-selector
|
|
401
|
+
|
|
402
|
+
**Presentational** — button, badge, card, alert, avatar, separator, skeleton,
|
|
403
|
+
spinner, kbd, aspect-ratio, progress, table, breadcrumb, pagination, label,
|
|
404
|
+
input, textarea, native-select, field, empty, item, button-group
|
|
405
|
+
|
|
406
|
+
**Form controls** — checkbox, radio-group, switch, toggle, toggle-group,
|
|
407
|
+
input-group, input-otp
|
|
408
|
+
|
|
409
|
+
**Interactive** — accordion, collapsible, tabs, dialog, alert-dialog, sheet,
|
|
410
|
+
dropdown-menu (including submenus), context-menu, menubar, popover, tooltip,
|
|
411
|
+
hover-card, select,
|
|
412
|
+
scroll-area, navigation-menu, slider, sidebar, drawer, carousel
|
|
413
|
+
|
|
414
|
+
**AI chat** — message, bubble, attachment, marker, message-scroller. The first
|
|
415
|
+
four are markup and variants with no behaviour of their own; the scroller
|
|
416
|
+
follows its own live end, holds the view still when older history loads above,
|
|
417
|
+
and is the one component here reimplemented from a package shadcn publishes
|
|
418
|
+
rather than from Radix.
|
|
419
|
+
|
|
420
|
+
These are one of the two places hand-written CSS was unavoidable: `shimmer`,
|
|
421
|
+
`scroll-fade-x`, `scroll-fade-b`, `scrollbar-none`, `scrollbar-thin` and
|
|
422
|
+
`scrollbar-gutter-stable` are shadcn's own utilities rather than Tailwind's, and
|
|
423
|
+
are reproduced at the end of `shadcn.css`. The drawer is the other.
|
|
424
|
+
|
|
425
|
+
**Drawer** — dragged down or thrown, it closes; dragged up it gives a little and
|
|
426
|
+
comes back. Upstream builds this on vaul rather than Radix, and vaul is a Radix
|
|
427
|
+
dialog with a drag on top, so the open/close half is the dialog's here too and
|
|
428
|
+
only the gesture is new. Four of vaul's features are deliberately not ported —
|
|
429
|
+
snap points, scaling the page behind the drawer, its iOS `position: fixed`
|
|
430
|
+
workaround and nested drawers. Its stylesheet is the second piece of
|
|
431
|
+
hand-written CSS: `drawer.tsx` renders no entrance animation and no
|
|
432
|
+
`touch-action`, and both are load-bearing.
|
|
433
|
+
|
|
434
|
+
**Reading direction** — write `dir="rtl"` on any ancestor and the components
|
|
435
|
+
that navigate with arrow keys follow it. shadcn ships a `DirectionProvider` for
|
|
436
|
+
this; there is none here, because the browser already resolves `dir` before a
|
|
437
|
+
Stimulus controller runs.
|
|
438
|
+
|
|
439
|
+
**Forms** — `shadcn_form_with` and `f.shadcn_input_field`, `f.shadcn_select`,
|
|
440
|
+
`f.shadcn_switch` and the rest. shadcn's Form component is react-hook-form's
|
|
441
|
+
per-field state given five wrappers; there is no such state on a server, and
|
|
442
|
+
what the wrappers do with it is what Rails' `FormBuilder` already does with a
|
|
443
|
+
model. So that family is a FormBuilder here rather than components, built over
|
|
444
|
+
`field`. It emits `field-*` slots where upstream's form emits `form-*`, errors
|
|
445
|
+
come from `ActiveModel::Errors` after a round trip rather than as you type, and
|
|
446
|
+
all of an attribute's messages are shown rather than the first.
|
|
447
|
+
|
|
448
|
+
**Toaster** — notifications, stacked one behind another and fanned out under
|
|
449
|
+
the pointer. shadcn's is a forty-line wrapper around `sonner` and renders no
|
|
450
|
+
markup of its own, so this one is not a port: it keeps sonner's measurements and
|
|
451
|
+
its stacking, and adds the two ways a Rails app actually raises a notification —
|
|
452
|
+
a flash rendered with the page, and a `turbo_stream.append` onto the list.
|
|
453
|
+
|
|
454
|
+
**Calendar** — a month is a `<table role="grid">`, built in Ruby with `Date` and
|
|
455
|
+
`I18n` rather than by `react-day-picker`: a third of that package is locale data
|
|
456
|
+
and another fifth is other calendar systems, both of which a Rails app already
|
|
457
|
+
has. It is the one component here that renders correctly with no JavaScript at
|
|
458
|
+
all; the controller adds the nav, the keyboard and the selection, and takes its
|
|
459
|
+
month names and its idea of *today* from the server rather than from the
|
|
460
|
+
browser's locale and clock.
|
|
461
|
+
|
|
462
|
+
**Chart** — `chart.tsx` draws nothing: it is a container that publishes
|
|
463
|
+
`--color-<key>` per series, plus the contents of a tooltip and a legend that
|
|
464
|
+
`recharts` fills in. That frame is ported 1:1 and the shapes are drawn here, as
|
|
465
|
+
SVG, from the server. The pie is the first — pass a Hash of key to number, which
|
|
466
|
+
is what `group(:x).sum(:y)` already returns. Bars, lines and areas are not drawn
|
|
467
|
+
yet.
|
|
468
|
+
|
|
469
|
+
**Resizable** — panels a pointer or the arrow keys can resize. Upstream wraps
|
|
470
|
+
`react-resizable-panels`; here a panel is a share of a flex container, so the
|
|
471
|
+
layout is the browser's and the controller only moves two numbers. The handle is
|
|
472
|
+
a `role="separator"` with the package's own keyboard: five points an arrow, all
|
|
473
|
+
the way on Home and End.
|
|
474
|
+
|
|
475
|
+
**Command** — the palette. Upstream wraps `cmdk`; here the items are
|
|
476
|
+
server-rendered and the controller filters, *ranks* and walks them. The ranking
|
|
477
|
+
matters enough that `cmdk`'s own fuzzy scorer is ported rather than replaced
|
|
478
|
+
with a substring match: type `gp` and Group Policy comes before Groups.
|
|
479
|
+
`keywords:` are searched and never shown.
|
|
480
|
+
|
|
481
|
+
**Combobox** — a field that filters a list and keeps the caret. It is the one
|
|
482
|
+
component shadcn writes against Base UI rather than Radix, so this family alone
|
|
483
|
+
emits `data-open` and reads `--anchor-width`; the port publishes those names
|
|
484
|
+
here and nowhere else. Single selection is complete; the chips markup is there
|
|
485
|
+
and adding one is not wired yet.
|
|
486
|
+
|
|
487
|
+
Every component in the registry is now ported, adapted or decided against with
|
|
488
|
+
a reason — see [the per-component notes](.claude/docs/features/README.md).
|
|
489
|
+
|
|
490
|
+
## What is and is not verified
|
|
491
|
+
|
|
492
|
+
Worth being precise about, because "1:1" invites more trust than any test here
|
|
493
|
+
earns.
|
|
494
|
+
|
|
495
|
+
| Spec | Catches |
|
|
496
|
+
|---|---|
|
|
497
|
+
| `parity_spec.rb` | a class upstream emits that the port dropped or mistyped. Per *family*, not per part — swap two variants' bodies and it stays green |
|
|
498
|
+
| `snapshot_spec.rb` | anything that changes rendered HTML: wrong part, wrong variant, attribute drift, extra classes |
|
|
499
|
+
| `stimulus_contract_spec.rb` | a controller action, target or value a component names but the JavaScript does not define |
|
|
500
|
+
| `system/` | the behaviour, in a real browser: open/close, keyboard navigation, focus trapping, positioning, persistence, Turbo Drive and morph refreshes |
|
|
501
|
+
| `system/accessibility_spec.rb` | axe over every preview, **in both palettes**, at rest and with each layer open |
|
|
502
|
+
| `form_builder_spec.rb`, `theming_spec.rb` | the Rails form wiring, the generated palettes and the switchers |
|
|
503
|
+
|
|
504
|
+
The system specs drive headless Chrome against the gallery, so they exercise the
|
|
505
|
+
compiled Tailwind and the actual Stimulus controllers — `popper.js`, the layer
|
|
506
|
+
stack in `dismiss.js` and the focus trap in `focus.js` included.
|
|
507
|
+
|
|
508
|
+
Two things remain unverified:
|
|
509
|
+
|
|
510
|
+
- **Parity runs one way.** When upstream *removes* a class, the port keeps it
|
|
511
|
+
and nothing fails. Read the TSX diff when you re-sync.
|
|
512
|
+
- **Accessibility is audited by axe, not by a person.** Every preview is checked
|
|
513
|
+
against WCAG 2.1 AA, at rest and with its layer open, in the light palette and
|
|
514
|
+
the dark one. axe catches names, roles, required parents and contrast; it does
|
|
515
|
+
not replace a screen reader, and nothing here has been through one.
|
|
516
|
+
|
|
517
|
+
The colour scheme the audit runs in is pinned, and that is not housekeeping:
|
|
518
|
+
headless Chrome follows the desktop it runs on, so the suite spent this
|
|
519
|
+
project's life auditing whichever palette the author was sitting in front of.
|
|
520
|
+
The first run on a Linux CI runner audited the other one and found eleven real
|
|
521
|
+
contrast violations.
|
|
522
|
+
|
|
523
|
+
### One thing to know if you have to meet AA
|
|
524
|
+
|
|
525
|
+
**shadcn's light palette puts `text-muted-foreground` on `bg-muted` at 4.34:1**,
|
|
526
|
+
where WCAG AA wants 4.5:1 — its own tokens, `muted: oklch(0.97 0 0)` against
|
|
527
|
+
`muted-foreground: oklch(0.556 0 0)`, and its own class string on the avatar
|
|
528
|
+
fallback (`avatar.tsx:49`). The same pair measures 5.85:1 in the dark palette.
|
|
529
|
+
`text-destructive` on `bg-destructive/10`, which the attachment's error state
|
|
530
|
+
and the destructive bubble use, comes to 4.00:1.
|
|
531
|
+
|
|
532
|
+
This port renders them unchanged, because upstream wins on markup and altering
|
|
533
|
+
them would put classes in your bundle that upstream does not emit. If you need
|
|
534
|
+
AA, override the token in your own CSS — one line, and it reaches every
|
|
535
|
+
component at once:
|
|
536
|
+
|
|
537
|
+
```css
|
|
538
|
+
:root { --muted-foreground: oklch(0.52 0 0); } /* measured: 5.04:1 on --muted */
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
That is the smallest step off upstream's `0.556` that clears AA — measured in
|
|
542
|
+
Chrome by painting both colours and computing the WCAG ratio, the same
|
|
543
|
+
arithmetic axe does.
|
|
544
|
+
|
|
545
|
+
## Known differences from the React DOM
|
|
546
|
+
|
|
547
|
+
Four deliberate ones, all documented at the point where they happen:
|
|
548
|
+
|
|
549
|
+
1. **Context-only roots render an element.** Radix's `Dialog.Root`,
|
|
550
|
+
`Popover.Root`, `Select.Root` and friends render no DOM at all. Stimulus
|
|
551
|
+
needs something to attach to, so those roots emit a `display: contents`
|
|
552
|
+
wrapper. It has no box and no effect on layout, and it gives shadcn's
|
|
553
|
+
`data-slot="dialog"` — which Radix silently drops — somewhere to live.
|
|
554
|
+
|
|
555
|
+
2. **Nothing is portalled to `document.body`.** Radix moves overlays and
|
|
556
|
+
floating content onto the body. Here they stay inside the component, wrapped
|
|
557
|
+
in the same `data-slot="*-portal"` / `data-radix-popper-content-wrapper`
|
|
558
|
+
elements, because moving them out of the controller's element would unbind
|
|
559
|
+
the Stimulus actions on the close buttons and menu items.
|
|
560
|
+
|
|
561
|
+
Staying put means a stacking context above the component could bury it —
|
|
562
|
+
`position: fixed` escapes overflow clipping but never a stacking context, and
|
|
563
|
+
a `sticky z-40` header or an `isolate` card is ordinary markup. The Popover
|
|
564
|
+
API solves exactly that: opening calls `showPopover()`, which paints the
|
|
565
|
+
layer above every stacking context *without* moving it in the DOM. Browsers
|
|
566
|
+
without the API fall back to plain `position: fixed`, which is correct except
|
|
567
|
+
under such an ancestor. `spec/system/stacking_context_spec.rb` holds this.
|
|
568
|
+
|
|
569
|
+
One case does remain: an ancestor with `transform`, `filter` or `contain`
|
|
570
|
+
becomes the containing block for fixed descendants, which affects where the
|
|
571
|
+
layer is positioned rather than what paints over it.
|
|
572
|
+
|
|
573
|
+
3. **A morph refresh resets a component to the server's state.** Turbo's morph
|
|
574
|
+
rewrites attributes in place without disconnecting the Stimulus controllers,
|
|
575
|
+
so `connect()` never runs again. The controllers re-sync on `turbo:morph`, so
|
|
576
|
+
the DOM and the controller always agree — but what a component was showing is
|
|
577
|
+
whatever the server just rendered. If a component's open state should survive
|
|
578
|
+
a refresh, mark it `data-turbo-permanent`; that is your call, not the
|
|
579
|
+
library's.
|
|
580
|
+
|
|
581
|
+
4. **Indicators are hidden, not unmounted, before JavaScript runs.** Radix
|
|
582
|
+
mounts a checkbox tick or a select checkmark only while checked. The server
|
|
583
|
+
renders them with `hidden` so the markup is right without JavaScript, and the
|
|
584
|
+
controller detaches them on connect to match Radix exactly.
|
|
585
|
+
|
|
586
|
+
## Development
|
|
587
|
+
|
|
588
|
+
```sh
|
|
589
|
+
bin/setup # bundle install + build Tailwind
|
|
590
|
+
bundle exec rake # the whole suite
|
|
591
|
+
bin/rubocop # Ruby style
|
|
592
|
+
bin/eslint # the controllers; Node is a dev dependency only
|
|
593
|
+
bin/console # IRB with the dummy app, to render components
|
|
594
|
+
cd test/dummy && bin/rails s
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
Then open <http://localhost:3000/lookbook> for the component gallery.
|
|
598
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for adding a component and re-syncing
|
|
599
|
+
with upstream.
|
|
600
|
+
|
|
601
|
+
## License
|
|
602
|
+
|
|
603
|
+
MIT. The vendored shadcn/ui sources under `vendor/shadcn` are MIT too; see
|
|
604
|
+
`vendor/shadcn/LICENSE.md`.
|