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
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// The measurements a message scroller runs on, ported from
|
|
2
|
+
// `vendor/shadcn-react/message-scroller/geometry.ts`.
|
|
3
|
+
//
|
|
4
|
+
// Every function here is pure: it reads rects, `scrollTop`, computed padding
|
|
5
|
+
// and two `data-` attributes, and returns a number, an element or null. Nothing
|
|
6
|
+
// writes, nothing schedules, nothing holds state — which is why this is a module
|
|
7
|
+
// of its own rather than methods on the controller, and why it can be tested
|
|
8
|
+
// without a browser opening anything.
|
|
9
|
+
//
|
|
10
|
+
// One function of upstream's is deliberately missing:
|
|
11
|
+
// `getMessageScrollerVisibilityState`, the largest in the file, which exists to
|
|
12
|
+
// feed `useMessageScrollerVisibility`. That surface is not reproduced — see
|
|
13
|
+
// `.claude/docs/features/message-scroller.md` for the measurement behind that
|
|
14
|
+
// decision. Nothing else in this file referenced it.
|
|
15
|
+
|
|
16
|
+
// Upstream's `EMPTY_MESSAGE_SCROLLER_SCROLLABLE` (types.ts:193). A frozen
|
|
17
|
+
// object rather than a fresh one per call, so a caller cannot mutate the answer
|
|
18
|
+
// for the next.
|
|
19
|
+
const NOT_SCROLLABLE = Object.freeze({ start: false, end: false })
|
|
20
|
+
|
|
21
|
+
// The rows, in document order. The spacer is a sibling of the items and has to
|
|
22
|
+
// be excluded by identity rather than by a selector: it carries no `data-slot`
|
|
23
|
+
// of its own, and upstream filters it the same way (geometry.ts:108-116).
|
|
24
|
+
export function getMessageScrollerItems(content, spacer) {
|
|
25
|
+
return Array.from(content.children).filter(
|
|
26
|
+
(child) => child instanceof HTMLElement && child !== spacer
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Whether there is anything to scroll to in each direction — which is what
|
|
31
|
+
// decides the two buttons' `data-active`. `end` is measured against the
|
|
32
|
+
// content's own bottom rather than `scrollHeight`, because the tail spacer
|
|
33
|
+
// inflates `scrollHeight` on purpose and would keep the button lit forever.
|
|
34
|
+
export function getMessageScrollerScrollable({
|
|
35
|
+
content,
|
|
36
|
+
scrollEdgeThreshold,
|
|
37
|
+
spacer,
|
|
38
|
+
viewport
|
|
39
|
+
}) {
|
|
40
|
+
if (!viewport || !content) return NOT_SCROLLABLE
|
|
41
|
+
|
|
42
|
+
const contentBottom = getContentBottom({ content, spacer, viewport })
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
start: viewport.scrollTop > scrollEdgeThreshold,
|
|
46
|
+
end:
|
|
47
|
+
contentBottom - viewport.scrollTop - viewport.clientHeight >
|
|
48
|
+
scrollEdgeThreshold
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// The first anchor among rows added since the last count — what a newly
|
|
53
|
+
// arrived turn scrolls to.
|
|
54
|
+
export function getNewScrollAnchor(items, previousItemCount) {
|
|
55
|
+
for (let index = previousItemCount; index < items.length; index++) {
|
|
56
|
+
if (items[index]?.dataset.scrollAnchor === "true") return items[index]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// `handledAnchors` is anything with a `has(element)` — upstream passes a
|
|
63
|
+
// `WeakSet` of anchors already scrolled to, so one is never handled twice.
|
|
64
|
+
export function getUnanchoredScrollAnchor(items, handledAnchors) {
|
|
65
|
+
for (const item of items) {
|
|
66
|
+
if (item.dataset.scrollAnchor === "true" && !handledAnchors.has(item)) {
|
|
67
|
+
return item
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Two turns arriving in one batch cannot both be scrolled to, so the caller
|
|
75
|
+
// needs to know before it tries. Stops at two rather than counting them all.
|
|
76
|
+
export function hasMultipleNewScrollAnchors(items, previousItemCount) {
|
|
77
|
+
let count = 0
|
|
78
|
+
|
|
79
|
+
for (let index = previousItemCount; index < items.length; index++) {
|
|
80
|
+
if (items[index]?.dataset.scrollAnchor !== "true") continue
|
|
81
|
+
|
|
82
|
+
count += 1
|
|
83
|
+
if (count > 1) return true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getLastScrollAnchor(items) {
|
|
90
|
+
for (let index = items.length - 1; index >= 0; index--) {
|
|
91
|
+
if (items[index]?.dataset.scrollAnchor === "true") return items[index]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Rows without a `data-message-id` are skipped: the spacer is already gone, but
|
|
98
|
+
// a host may put its own markup in the content column and it should not count
|
|
99
|
+
// as a message.
|
|
100
|
+
export function getFirstVisibleMessageItem({ content, spacer, viewport }) {
|
|
101
|
+
const viewportRect = viewport.getBoundingClientRect()
|
|
102
|
+
|
|
103
|
+
for (const item of getMessageScrollerItems(content, spacer)) {
|
|
104
|
+
if (!item.dataset.messageId) continue
|
|
105
|
+
|
|
106
|
+
const rect = item.getBoundingClientRect()
|
|
107
|
+
|
|
108
|
+
if (rect.bottom > viewportRect.top && rect.top < viewportRect.bottom) {
|
|
109
|
+
return item
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Where the viewport has to be scrolled for `element` to sit at `align`.
|
|
117
|
+
// `scrollMargin` is the caller's breathing room above it; the content's own
|
|
118
|
+
// block padding is subtracted so the row lands where it looks right rather than
|
|
119
|
+
// where the box model puts it (geometry.ts:204-265).
|
|
120
|
+
export function getElementScrollTop({
|
|
121
|
+
align,
|
|
122
|
+
element,
|
|
123
|
+
scrollMargin,
|
|
124
|
+
spacer,
|
|
125
|
+
viewport
|
|
126
|
+
}) {
|
|
127
|
+
const elementTop = getElementTop(element, viewport)
|
|
128
|
+
const elementHeight = element.getBoundingClientRect().height
|
|
129
|
+
const contentPadding = getContentBlockPadding(spacer)
|
|
130
|
+
|
|
131
|
+
if (align === "center") {
|
|
132
|
+
const insetHeight = Math.max(
|
|
133
|
+
0,
|
|
134
|
+
viewport.clientHeight - contentPadding.start - contentPadding.end
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
elementTop -
|
|
139
|
+
contentPadding.start -
|
|
140
|
+
(insetHeight - elementHeight) / 2 -
|
|
141
|
+
scrollMargin
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (align === "end") {
|
|
146
|
+
return (
|
|
147
|
+
elementTop -
|
|
148
|
+
viewport.clientHeight +
|
|
149
|
+
elementHeight +
|
|
150
|
+
contentPadding.end +
|
|
151
|
+
scrollMargin
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// `nearest` is the only one that can answer "do not move".
|
|
156
|
+
if (align === "nearest") {
|
|
157
|
+
const elementBottom = elementTop + elementHeight
|
|
158
|
+
const viewportTop = viewport.scrollTop + contentPadding.start
|
|
159
|
+
const viewportBottom =
|
|
160
|
+
viewport.scrollTop + viewport.clientHeight - contentPadding.end
|
|
161
|
+
|
|
162
|
+
if (elementTop >= viewportTop && elementBottom <= viewportBottom) {
|
|
163
|
+
return viewport.scrollTop
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (elementTop < viewportTop) {
|
|
167
|
+
return elementTop - contentPadding.start - scrollMargin
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
elementBottom - viewport.clientHeight + contentPadding.end + scrollMargin
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return elementTop - contentPadding.start - scrollMargin
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// In the scroll container's own coordinates, not the viewport's: the rect
|
|
179
|
+
// difference plus the current `scrollTop` is what makes the answer survive
|
|
180
|
+
// being scrolled.
|
|
181
|
+
export function getElementTop(element, viewport) {
|
|
182
|
+
const elementRect = element.getBoundingClientRect()
|
|
183
|
+
const viewportRect = viewport.getBoundingClientRect()
|
|
184
|
+
|
|
185
|
+
return elementRect.top - viewportRect.top + viewport.scrollTop
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function getElementViewportTop(element, viewport) {
|
|
189
|
+
return (
|
|
190
|
+
element.getBoundingClientRect().top - viewport.getBoundingClientRect().top
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// How tall the spacer under the last message has to be for that message to be
|
|
195
|
+
// able to reach the top of the viewport. Negative means none is needed.
|
|
196
|
+
export function getTailSpacerHeight({ content, scrollTop, spacer, viewport }) {
|
|
197
|
+
const contentBottom = getContentBottom({ content, spacer, viewport })
|
|
198
|
+
|
|
199
|
+
return scrollTop + viewport.clientHeight - contentBottom
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// The bottom of the real content, spacer excluded — measured from the rows
|
|
203
|
+
// rather than read off `scrollHeight`, which the spacer inflates. Starts at the
|
|
204
|
+
// padding so an empty column still answers sensibly.
|
|
205
|
+
export function getContentBottom({ content, spacer, viewport }) {
|
|
206
|
+
const items = getMessageScrollerItems(content, spacer)
|
|
207
|
+
const padding = getBlockPadding(content)
|
|
208
|
+
const viewportRect = viewport.getBoundingClientRect()
|
|
209
|
+
const scrollTop = viewport.scrollTop
|
|
210
|
+
let contentBottom = padding.start + padding.end
|
|
211
|
+
|
|
212
|
+
for (const item of items) {
|
|
213
|
+
const rect = item.getBoundingClientRect()
|
|
214
|
+
|
|
215
|
+
contentBottom = Math.max(
|
|
216
|
+
contentBottom,
|
|
217
|
+
rect.bottom - viewportRect.top + scrollTop + padding.end
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return contentBottom
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function getMaxScrollTop(viewport) {
|
|
225
|
+
return Math.max(0, viewport.scrollHeight - viewport.clientHeight)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// `paddingBlock*` first so a vertical writing mode measures the axis that
|
|
229
|
+
// actually scrolls; `paddingTop`/`paddingBottom` is the fallback.
|
|
230
|
+
export function getBlockPadding(element) {
|
|
231
|
+
const style = window.getComputedStyle(element)
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
end: readCssPixel(style.paddingBlockEnd || style.paddingBottom),
|
|
235
|
+
start: readCssPixel(style.paddingBlockStart || style.paddingTop)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Reached through the spacer rather than by taking the content element as an
|
|
240
|
+
// argument, because the callers that need it are holding the spacer.
|
|
241
|
+
export function getContentBlockPadding(spacer) {
|
|
242
|
+
const content = spacer?.parentElement
|
|
243
|
+
|
|
244
|
+
if (!content) return { end: 0, start: 0 }
|
|
245
|
+
|
|
246
|
+
return getBlockPadding(content)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function getFlexGap(element) {
|
|
250
|
+
if (!element) return 0
|
|
251
|
+
|
|
252
|
+
const style = window.getComputedStyle(element)
|
|
253
|
+
const gap = style.rowGap === "normal" ? style.gap : style.rowGap
|
|
254
|
+
|
|
255
|
+
return readCssPixel(gap)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// `parseFloat` on a computed length, with everything unparseable — `normal`,
|
|
259
|
+
// `auto`, an empty string — collapsing to 0 rather than to `NaN`, which would
|
|
260
|
+
// poison every sum it reaches.
|
|
261
|
+
function readCssPixel(value) {
|
|
262
|
+
if (!value) return 0
|
|
263
|
+
|
|
264
|
+
const number = Number.parseFloat(value)
|
|
265
|
+
|
|
266
|
+
return Number.isFinite(number) ? number : 0
|
|
267
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// The theming store — the port of what `next-themes` does for shadcn's own
|
|
2
|
+
// site, plus the colour-theme switching from its `ActiveThemeProvider`.
|
|
3
|
+
//
|
|
4
|
+
// Two independent axes, exactly as upstream:
|
|
5
|
+
//
|
|
6
|
+
// mode "light" | "dark" | "system" → toggles `.dark` on <html>
|
|
7
|
+
// theme a palette name → swaps `theme-*` on <body>
|
|
8
|
+
//
|
|
9
|
+
// Both are kept in `localStorage` and mirrored into a cookie, so a Rails layout
|
|
10
|
+
// can render the right classes server-side and avoid a flash without relying on
|
|
11
|
+
// the inline script.
|
|
12
|
+
|
|
13
|
+
export const MODE_STORAGE_KEY = "shadcn-ui-mode"
|
|
14
|
+
export const THEME_STORAGE_KEY = "shadcn-ui-theme"
|
|
15
|
+
export const CHANGE_EVENT = "shadcn:theme-change"
|
|
16
|
+
|
|
17
|
+
export const MODES = [ "light", "dark", "system" ]
|
|
18
|
+
|
|
19
|
+
const DARK_QUERY = "(prefers-color-scheme: dark)"
|
|
20
|
+
|
|
21
|
+
function read(key, fallback) {
|
|
22
|
+
try {
|
|
23
|
+
return window.localStorage.getItem(key) || fallback
|
|
24
|
+
} catch {
|
|
25
|
+
// Private browsing, or storage disabled.
|
|
26
|
+
return fallback
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function write(key, value) {
|
|
31
|
+
try {
|
|
32
|
+
window.localStorage.setItem(key, value)
|
|
33
|
+
} catch {
|
|
34
|
+
// Ignore: the cookie mirror below is enough to survive a reload.
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
document.cookie = `${key}=${encodeURIComponent(value)}; path=/; max-age=31536000; samesite=lax`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function systemMode() {
|
|
41
|
+
return window.matchMedia(DARK_QUERY).matches ? "dark" : "light"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getMode() {
|
|
45
|
+
const mode = read(MODE_STORAGE_KEY, "system")
|
|
46
|
+
return MODES.includes(mode) ? mode : "system"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The mode actually in effect: `system` resolved against the OS preference.
|
|
50
|
+
export function resolvedMode() {
|
|
51
|
+
const mode = getMode()
|
|
52
|
+
return mode === "system" ? systemMode() : mode
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function setMode(mode) {
|
|
56
|
+
if (!MODES.includes(mode)) return
|
|
57
|
+
|
|
58
|
+
write(MODE_STORAGE_KEY, mode)
|
|
59
|
+
applyMode()
|
|
60
|
+
announce()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function toggleMode() {
|
|
64
|
+
setMode(resolvedMode() === "dark" ? "light" : "dark")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// The palette in effect: what the visitor chose, or what the *document* says.
|
|
68
|
+
//
|
|
69
|
+
// The fallback used to be the literal "neutral", and that threw away the
|
|
70
|
+
// server's answer. `shadcn_theme_script_tag` writes the theme the page was
|
|
71
|
+
// rendered with onto `<html>` (`data-shadcn-theme`), and a host that sets
|
|
72
|
+
// `shadcn_theme_class(default: "sky")` renders `<body class="theme-sky">` — and
|
|
73
|
+
// then `applyTheme()` ran, found nothing in storage, and swapped the class back
|
|
74
|
+
// to `theme-neutral` on the first paint. The palette a host had chosen was
|
|
75
|
+
// visible only until the JavaScript booted, with nothing to explain it.
|
|
76
|
+
//
|
|
77
|
+
// Reading the document keeps one source of truth: the script tag has already
|
|
78
|
+
// resolved storage against the host's default, so this returns the same value
|
|
79
|
+
// when the visitor has chosen, and the host's when they have not.
|
|
80
|
+
export function getTheme() {
|
|
81
|
+
return read(THEME_STORAGE_KEY, documentTheme())
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Two places a host publishes it, and the rendered class comes first.
|
|
85
|
+
//
|
|
86
|
+
// The class is what the **server** decided, reading the cookie this module
|
|
87
|
+
// mirrors every choice into and falling back to the host's own default. The
|
|
88
|
+
// attribute is what the script tag decided a moment later, reading
|
|
89
|
+
// `localStorage` against the same default. They agree whenever both are
|
|
90
|
+
// present — the cookie is the mirror of the storage — and where they differ the
|
|
91
|
+
// class is the better informed of the two: storage cleared with the cookie
|
|
92
|
+
// still there is a visitor whose choice the server can still see and the script
|
|
93
|
+
// cannot.
|
|
94
|
+
function documentTheme() {
|
|
95
|
+
const target = document.body || document.documentElement
|
|
96
|
+
const rendered = Array.from(target.classList).find((name) =>
|
|
97
|
+
name.startsWith("theme-")
|
|
98
|
+
)
|
|
99
|
+
if (rendered) return rendered.slice("theme-".length)
|
|
100
|
+
|
|
101
|
+
return document.documentElement.dataset.shadcnTheme || "neutral"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function setTheme(theme) {
|
|
105
|
+
if (!theme) return
|
|
106
|
+
|
|
107
|
+
write(THEME_STORAGE_KEY, theme)
|
|
108
|
+
applyTheme()
|
|
109
|
+
announce()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function applyMode() {
|
|
113
|
+
const root = document.documentElement
|
|
114
|
+
|
|
115
|
+
root.classList.toggle("dark", resolvedMode() === "dark")
|
|
116
|
+
root.style.colorScheme = resolvedMode()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function applyTheme() {
|
|
120
|
+
const target = document.body
|
|
121
|
+
if (!target) return
|
|
122
|
+
|
|
123
|
+
// Resolved **before** the classes come off. `getTheme()` falls back to what
|
|
124
|
+
// the document already says, and the loop below is what erases the answer:
|
|
125
|
+
// asking afterwards read a body with no `theme-*` left on it, and every boot
|
|
126
|
+
// fell back to the default no matter what the server had rendered.
|
|
127
|
+
const theme = getTheme()
|
|
128
|
+
|
|
129
|
+
for (const name of Array.from(target.classList)) {
|
|
130
|
+
if (name.startsWith("theme-")) target.classList.remove(name)
|
|
131
|
+
}
|
|
132
|
+
target.classList.add(`theme-${theme}`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function apply() {
|
|
136
|
+
applyMode()
|
|
137
|
+
applyTheme()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function announce() {
|
|
141
|
+
document.dispatchEvent(
|
|
142
|
+
new CustomEvent(CHANGE_EVENT, {
|
|
143
|
+
detail: { mode: getMode(), resolvedMode: resolvedMode(), theme: getTheme() }
|
|
144
|
+
})
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Follow the OS while the mode is "system", like next-themes does.
|
|
149
|
+
let watching = false
|
|
150
|
+
|
|
151
|
+
export function watchSystem() {
|
|
152
|
+
if (watching) return
|
|
153
|
+
watching = true
|
|
154
|
+
|
|
155
|
+
window.matchMedia(DARK_QUERY).addEventListener("change", () => {
|
|
156
|
+
if (getMode() !== "system") return
|
|
157
|
+
|
|
158
|
+
applyMode()
|
|
159
|
+
announce()
|
|
160
|
+
})
|
|
161
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Promotes floating content to the browser's top layer.
|
|
2
|
+
//
|
|
3
|
+
// `position: fixed` escapes overflow clipping but never escapes a *stacking
|
|
4
|
+
// context*: a layer's `z-50` is scoped to the nearest positioned ancestor with a
|
|
5
|
+
// z-index, so a `sticky z-40` header, an `isolate` card or anything with
|
|
6
|
+
// `opacity < 1` can bury it. No z-index can fix that from the inside.
|
|
7
|
+
//
|
|
8
|
+
// The Popover API does. `showPopover()` paints the element above every stacking
|
|
9
|
+
// context while leaving it exactly where it is in the DOM — which is what makes
|
|
10
|
+
// it usable here, since moving it would unbind the Stimulus actions on the close
|
|
11
|
+
// buttons and menu items.
|
|
12
|
+
//
|
|
13
|
+
// `manual` rather than `auto`: `auto` brings its own light-dismiss and its own
|
|
14
|
+
// nesting stack, which would fight the layer stack in `dismiss.js`.
|
|
15
|
+
|
|
16
|
+
export const SUPPORTED =
|
|
17
|
+
typeof HTMLElement !== "undefined" && Object.hasOwn(HTMLElement.prototype, "popover")
|
|
18
|
+
|
|
19
|
+
// Older browsers keep the previous behaviour: still `position: fixed`, still
|
|
20
|
+
// correct in the common case, just vulnerable to a stacking context above it.
|
|
21
|
+
const usable = (element) => SUPPORTED && !!element
|
|
22
|
+
|
|
23
|
+
export function enable(element) {
|
|
24
|
+
if (!usable(element)) return false
|
|
25
|
+
|
|
26
|
+
element.popover = "manual"
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function show(element) {
|
|
31
|
+
if (!usable(element) || !element.popover) return false
|
|
32
|
+
|
|
33
|
+
// Throws if it is already showing, or not connected yet.
|
|
34
|
+
try { element.showPopover() } catch { /* already open */ }
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function hide(element) {
|
|
39
|
+
if (!usable(element) || !element.popover) return false
|
|
40
|
+
|
|
41
|
+
try { element.hidePopover() } catch { /* already closed */ }
|
|
42
|
+
return true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Hiding a popover does not put it back in the page's flow: the UA gives
|
|
46
|
+
// `[popover]` `position: fixed`, and that applies open or closed. `shadcn.css`
|
|
47
|
+
// neutralises the rest of those defaults but deliberately not `position`, which
|
|
48
|
+
// every other caller here wants — they enable it on a wrapper that is fixed
|
|
49
|
+
// anyway.
|
|
50
|
+
//
|
|
51
|
+
// The Sidebar is the one caller that enables it on an element the page is laid
|
|
52
|
+
// out around. Left enabled, the panel stays out of flow after the mobile sheet
|
|
53
|
+
// closes, and the page is drawn over a sidebar that is still visible.
|
|
54
|
+
export function disable(element) {
|
|
55
|
+
if (!usable(element)) return false
|
|
56
|
+
|
|
57
|
+
element.removeAttribute("popover")
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// A one-second character buffer shared by any roving-focus list (menu items,
|
|
2
|
+
// select options): each keystroke is appended to the buffer, which clears
|
|
3
|
+
// itself after a second of silence, and the caller gets back the item to
|
|
4
|
+
// move the highlight to next, or nothing when there is nowhere new to move —
|
|
5
|
+
// ported from Radix's `findNextItem` (vendor/radix/ui/select.tsx:1906-1921),
|
|
6
|
+
// whose body is identical to the dropdown menu's own `getNextMatch`
|
|
7
|
+
// (vendor/radix/ui/menu.tsx:1336-1347), so the search starts at the item
|
|
8
|
+
// currently highlighted and wraps, rather than always scanning from the top
|
|
9
|
+
// of the list.
|
|
10
|
+
//
|
|
11
|
+
// The input differs in Radix's *menu*, not in the file above: `getNextMatch`
|
|
12
|
+
// is handed an array of label strings and the winner mapped back to an item
|
|
13
|
+
// (vendor/radix/ui/menu.tsx:451-454), so two items sharing a label are one
|
|
14
|
+
// candidate there and two here. `findNextItem`'s own callers pass items and
|
|
15
|
+
// compare by identity, exactly as this does.
|
|
16
|
+
//
|
|
17
|
+
// **That difference is kept, and it is a decision rather than an oversight.**
|
|
18
|
+
// With `["Copy", "Copy", "Delete"]` and the second Copy highlighted, Radix's
|
|
19
|
+
// menu takes `values.indexOf("Copy")` — which is 0, the *first* one — then the
|
|
20
|
+
// single-character filter drops every value equal to the current match, so both
|
|
21
|
+
// Copies leave the candidates and the highlight does not move: the second
|
|
22
|
+
// duplicate is unreachable by typing. Here the two are separate elements, so
|
|
23
|
+
// pressing `c` walks from one to the other, which is what a typeahead is for
|
|
24
|
+
// and what a person with two identically named commands in a menu expects.
|
|
25
|
+
//
|
|
26
|
+
// Nothing else diverges: Radix's own *select* compares by identity too, and
|
|
27
|
+
// this is a port of that file. `spec/system/dropdown_menu_spec.rb` pins the
|
|
28
|
+
// duplicate-label case so the choice cannot be undone by accident.
|
|
29
|
+
//
|
|
30
|
+
// Deliberately does not know how the caller moves focus — that stays in each
|
|
31
|
+
// controller, which owns its own item list and highlighting. The current
|
|
32
|
+
// item is a parameter rather than something read off the DOM here.
|
|
33
|
+
export class Typeahead {
|
|
34
|
+
constructor() {
|
|
35
|
+
this.buffer = ""
|
|
36
|
+
this.timer = null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
search(character, items, currentItem) {
|
|
40
|
+
clearTimeout(this.timer)
|
|
41
|
+
// Raw case, not lowercased: Radix compares the raw characters to decide
|
|
42
|
+
// whether they repeat (select.tsx:1911) and only lowercases for the
|
|
43
|
+
// `startsWith` match (select.tsx:1918) — "Bb" is not a repeat of "b".
|
|
44
|
+
this.buffer += character
|
|
45
|
+
this.timer = setTimeout(() => (this.buffer = ""), 1000)
|
|
46
|
+
|
|
47
|
+
return findNextItem(items, this.buffer, currentItem)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Drops the buffer without waiting for its second to elapse. Radix does this
|
|
51
|
+
// at a *different moment* in each of the two components this class serves:
|
|
52
|
+
// the select on open — "reset typeahead when we open",
|
|
53
|
+
// vendor/radix/ui/select.tsx:331-336 — and the menu whenever focus leaves the
|
|
54
|
+
// content, vendor/radix/ui/menu.tsx:585-590. So the moment is each
|
|
55
|
+
// controller's to choose, and neither is hardcoded here.
|
|
56
|
+
reset() {
|
|
57
|
+
clearTimeout(this.timer)
|
|
58
|
+
this.buffer = ""
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// This is the "meat" of the typeahead matching logic (vendor/radix/ui/select.tsx:1906-1921,
|
|
63
|
+
// vendor/radix/ui/menu.tsx:1336-1347). It takes a list of items, the search
|
|
64
|
+
// and the current item, and returns the next item (or `undefined`).
|
|
65
|
+
//
|
|
66
|
+
// The search is normalized because if a user has repeatedly pressed a
|
|
67
|
+
// character, we want the exact same behavior as if we only had that one
|
|
68
|
+
// character (ie. cycle through items starting with that character).
|
|
69
|
+
//
|
|
70
|
+
// The items are also reordered by wrapping the array around the current
|
|
71
|
+
// item, so we always look forward from the current item and picking the
|
|
72
|
+
// first match is always the correct one.
|
|
73
|
+
//
|
|
74
|
+
// Finally, if the normalized search is exactly one character, the current
|
|
75
|
+
// item is excluded from the candidates, because otherwise it would always be
|
|
76
|
+
// the first to match and focus would never move. This is as opposed to the
|
|
77
|
+
// regular case, where focus should stay put if the current item still
|
|
78
|
+
// matches.
|
|
79
|
+
function findNextItem(items, search, currentItem) {
|
|
80
|
+
const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0])
|
|
81
|
+
const normalizedSearch = isRepeated ? search[0] : search
|
|
82
|
+
const currentItemIndex = currentItem ? items.indexOf(currentItem) : -1
|
|
83
|
+
let wrapped = wrapArray(items, Math.max(currentItemIndex, 0))
|
|
84
|
+
|
|
85
|
+
if (normalizedSearch.length === 1) wrapped = wrapped.filter((item) => item !== currentItem)
|
|
86
|
+
|
|
87
|
+
const nextItem = wrapped.find((item) =>
|
|
88
|
+
item.textContent.trim().toLowerCase().startsWith(normalizedSearch.toLowerCase())
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return nextItem !== currentItem ? nextItem : undefined
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Wraps an array around itself at a given start index
|
|
95
|
+
// (vendor/radix/ui/select.tsx:1923-1929).
|
|
96
|
+
// Example: `wrapArray([ "a", "b", "c", "d" ], 2)` is `[ "c", "d", "a", "b" ]`.
|
|
97
|
+
function wrapArray(array, startIndex) {
|
|
98
|
+
return array.map((_, index) => array[(startIndex + index) % array.length])
|
|
99
|
+
}
|
data/config/importmap.rb
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Every user-visible string the components render. shadcn hardcodes these in
|
|
2
|
+
# English; a Rails app is likely not English-only, so they go through I18n with
|
|
3
|
+
# the upstream text as the default. Override any key in your own locale files.
|
|
4
|
+
en:
|
|
5
|
+
shadcn_view_component:
|
|
6
|
+
breadcrumb:
|
|
7
|
+
label: breadcrumb
|
|
8
|
+
more: More
|
|
9
|
+
calendar:
|
|
10
|
+
previous: Go to the Previous Month
|
|
11
|
+
next: Go to the Next Month
|
|
12
|
+
today_label: "Today, %{date}"
|
|
13
|
+
selected_label: "%{date}, selected"
|
|
14
|
+
week_number_header: Week Number
|
|
15
|
+
week_number: "Week %{number}"
|
|
16
|
+
choose_month: Choose the Month
|
|
17
|
+
choose_year: Choose the Year
|
|
18
|
+
chart:
|
|
19
|
+
label: Chart
|
|
20
|
+
value: Value
|
|
21
|
+
combobox:
|
|
22
|
+
clear: Clear
|
|
23
|
+
toggle: Show options
|
|
24
|
+
remove: Remove
|
|
25
|
+
# The chips field's accessible name, when the caller gave it no
|
|
26
|
+
# placeholder to borrow one from.
|
|
27
|
+
add: Add an item
|
|
28
|
+
carousel:
|
|
29
|
+
previous: Previous slide
|
|
30
|
+
next: Next slide
|
|
31
|
+
dialog:
|
|
32
|
+
close: Close
|
|
33
|
+
pagination:
|
|
34
|
+
label: pagination
|
|
35
|
+
previous: Previous
|
|
36
|
+
next: Next
|
|
37
|
+
go_to_previous: Go to previous page
|
|
38
|
+
go_to_next: Go to next page
|
|
39
|
+
more: More pages
|
|
40
|
+
toaster:
|
|
41
|
+
label: Notifications
|
|
42
|
+
dismiss: Dismiss
|
|
43
|
+
select:
|
|
44
|
+
search_label: Search options
|
|
45
|
+
empty: No results found.
|
|
46
|
+
dialog_label: Select an option
|
|
47
|
+
list_label: Options
|
|
48
|
+
message_scroller:
|
|
49
|
+
scroll_to_end: Scroll to end
|
|
50
|
+
scroll_to_start: Scroll to start
|
|
51
|
+
sidebar:
|
|
52
|
+
toggle: Toggle Sidebar
|
|
53
|
+
title: Sidebar
|
|
54
|
+
description: Displays the mobile sidebar.
|
|
55
|
+
spinner:
|
|
56
|
+
loading: Loading
|
|
57
|
+
theme:
|
|
58
|
+
toggle: Toggle theme
|
|
59
|
+
label: Theme
|
|
60
|
+
light: Light
|
|
61
|
+
dark: Dark
|
|
62
|
+
system: System
|