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,154 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { uniqueId } from "shadcn/id"
|
|
3
|
+
import { pushLayer, removeLayer } from "shadcn/dismiss"
|
|
4
|
+
import { trapFocus, focusFirst, lockScroll, unlockScroll } from "shadcn/focus"
|
|
5
|
+
import * as topLayer from "shadcn/top_layer"
|
|
6
|
+
import { ExitQueue } from "shadcn/animation"
|
|
7
|
+
|
|
8
|
+
// Radix's Dialog, shared by Dialog, AlertDialog and Sheet: focus is trapped in
|
|
9
|
+
// the content, body scrolling is locked, and Escape closes it — except for
|
|
10
|
+
// AlertDialog, which Radix keeps dismiss-proof on outside clicks.
|
|
11
|
+
//
|
|
12
|
+
// Radix portals the overlay and content onto `document.body`; we deliberately
|
|
13
|
+
// leave them where they are. Both are `position: fixed` and the root is
|
|
14
|
+
// `display: contents`, so they still lay out against the viewport, and keeping
|
|
15
|
+
// them inside the controller's element is what lets the Stimulus actions on
|
|
16
|
+
// the close buttons keep working. (The one case this gives up is a host page
|
|
17
|
+
// that puts a `transform`/`filter`/`contain` ancestor above the dialog, which
|
|
18
|
+
// would become the containing block.)
|
|
19
|
+
export default class extends Controller {
|
|
20
|
+
static targets = [ "trigger", "overlay", "content" ]
|
|
21
|
+
static values = {
|
|
22
|
+
open: Boolean,
|
|
23
|
+
modal: { type: Boolean, default: true },
|
|
24
|
+
dismissable: { type: Boolean, default: true }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
connect() {
|
|
28
|
+
this.releaseFocus = null
|
|
29
|
+
this.layer = null
|
|
30
|
+
this.exits = new ExitQueue()
|
|
31
|
+
|
|
32
|
+
this.render()
|
|
33
|
+
if (this.openValue) this.show()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
disconnect() {
|
|
37
|
+
this.teardown()
|
|
38
|
+
// Turbo may be detaching the element; a continuation would then be
|
|
39
|
+
// operating on a subtree that has left the document.
|
|
40
|
+
this.exits.flushAll()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
open() {
|
|
44
|
+
this.show()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
close() {
|
|
48
|
+
this.hide()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
toggle() {
|
|
52
|
+
this.openValue ? this.hide() : this.show()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
show() {
|
|
56
|
+
if (!this.hasContentTarget || this.layer) return
|
|
57
|
+
|
|
58
|
+
this.previouslyFocused = document.activeElement
|
|
59
|
+
this.openValue = true
|
|
60
|
+
this.render()
|
|
61
|
+
|
|
62
|
+
if (this.modalValue) lockScroll()
|
|
63
|
+
|
|
64
|
+
this.releaseFocus = trapFocus(this.contentTarget, this.previouslyFocused)
|
|
65
|
+
focusFirst(this.contentTarget)
|
|
66
|
+
|
|
67
|
+
this.layer = pushLayer({
|
|
68
|
+
element: this.contentTarget,
|
|
69
|
+
anchors: this.hasTriggerTarget ? [ this.triggerTarget ] : [],
|
|
70
|
+
onDismiss: ({ reason }) => {
|
|
71
|
+
if (reason === "pointerdown" && !this.dismissableValue) return
|
|
72
|
+
this.hide()
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
this.dispatch("open")
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
hide() {
|
|
80
|
+
if (!this.layer) return
|
|
81
|
+
|
|
82
|
+
this.teardown()
|
|
83
|
+
this.openValue = false
|
|
84
|
+
this.render()
|
|
85
|
+
this.dispatch("close")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
teardown() {
|
|
89
|
+
if (this.layer) removeLayer(this.layer)
|
|
90
|
+
this.layer = null
|
|
91
|
+
|
|
92
|
+
if (this.releaseFocus) this.releaseFocus()
|
|
93
|
+
this.releaseFocus = null
|
|
94
|
+
|
|
95
|
+
if (this.modalValue) unlockScroll()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
render() {
|
|
99
|
+
const state = this.openValue ? "open" : "closed"
|
|
100
|
+
|
|
101
|
+
if (this.hasContentTarget) this.contentTarget.id ||= uniqueId("shadcn-dialog")
|
|
102
|
+
|
|
103
|
+
for (const element of this.layers) {
|
|
104
|
+
element.dataset.state = state
|
|
105
|
+
|
|
106
|
+
// A modal inside a `sticky z-40` header or an `isolate` card would
|
|
107
|
+
// otherwise be buried by whatever sits above that stacking context.
|
|
108
|
+
topLayer.enable(element)
|
|
109
|
+
|
|
110
|
+
if (this.openValue) {
|
|
111
|
+
// The overlay is faster than the content, so it can already have left
|
|
112
|
+
// the top layer while the content is still in it. Showing it again
|
|
113
|
+
// without taking the content out too would stack the backdrop *above*
|
|
114
|
+
// the dialog.
|
|
115
|
+
if (this.exits.has(element)) topLayer.hide(element)
|
|
116
|
+
this.exits.cancel(element)
|
|
117
|
+
element.hidden = false
|
|
118
|
+
topLayer.show(element)
|
|
119
|
+
} else if (element.hidden) {
|
|
120
|
+
// A morph can rewrite `hidden` back to true without going through an
|
|
121
|
+
// exit — `hidePopover()` on a never-shown popover costs nothing, so
|
|
122
|
+
// there is no reason to skip it just because nothing is animating.
|
|
123
|
+
topLayer.hide(element)
|
|
124
|
+
} else {
|
|
125
|
+
// Each element waits on its own animations: dialog content is
|
|
126
|
+
// `duration-200` (sheet content `duration-300`), while the overlay
|
|
127
|
+
// carries no `duration-*` class and falls back to `animate-out`'s
|
|
128
|
+
// default of 150ms.
|
|
129
|
+
this.exits.defer(element, () => {
|
|
130
|
+
element.hidden = true
|
|
131
|
+
topLayer.hide(element)
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!this.hasTriggerTarget) return
|
|
137
|
+
|
|
138
|
+
this.triggerTarget.dataset.state = state
|
|
139
|
+
this.triggerTarget.setAttribute("aria-expanded", String(this.openValue))
|
|
140
|
+
// The id is generated above, so the server has nothing to point this at.
|
|
141
|
+
if (this.hasContentTarget) {
|
|
142
|
+
this.triggerTarget.setAttribute("aria-controls", this.contentTarget.id)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Overlay first, content second: the top layer stacks in the order things are
|
|
147
|
+
// shown, so the dialog ends up above its own backdrop.
|
|
148
|
+
get layers() {
|
|
149
|
+
return [
|
|
150
|
+
this.hasOverlayTarget && this.overlayTarget,
|
|
151
|
+
this.hasContentTarget && this.contentTarget
|
|
152
|
+
].filter(Boolean)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// vaul's own numbers (vaul/src/constants.ts). A release counts as a throw above
|
|
4
|
+
// 0.4 px/ms whatever the distance; below that it closes only once a quarter of
|
|
5
|
+
// the drawer has been dragged off screen.
|
|
6
|
+
const VELOCITY_THRESHOLD = 0.4
|
|
7
|
+
const CLOSE_THRESHOLD = 0.25
|
|
8
|
+
|
|
9
|
+
// After a scroll inside the drawer, a press within this window cannot start a
|
|
10
|
+
// drag — otherwise the finish of a flick-scroll drags the drawer away.
|
|
11
|
+
const SCROLL_LOCK_TIMEOUT = 100
|
|
12
|
+
|
|
13
|
+
// A press landing while the drawer is still coming in scrolls rather than drags.
|
|
14
|
+
const OPEN_GRACE = 500
|
|
15
|
+
|
|
16
|
+
// Dragging *into* the drawer has nowhere to go, so it is rubber-banded rather
|
|
17
|
+
// than stopped dead (vaul/src/helpers.ts:90-92).
|
|
18
|
+
const dampen = (value) => 8 * (Math.log(value + 1) - 2)
|
|
19
|
+
|
|
20
|
+
const VERTICAL = new Set([ "top", "bottom" ])
|
|
21
|
+
|
|
22
|
+
// A press that lands on one of these means the control, not the panel. Vaul's
|
|
23
|
+
// own `[data-vaul-no-drag]` is the escape hatch for anything else — it was
|
|
24
|
+
// already honoured, but only in `shouldDrag`, which runs on the first *move*,
|
|
25
|
+
// and by then the capture below has already happened.
|
|
26
|
+
const CONTROLS = "a, button, input, select, textarea, label, [data-vaul-no-drag]"
|
|
27
|
+
|
|
28
|
+
// The drag half of vaul's Drawer, which is the only half that is vaul's: the
|
|
29
|
+
// rest of this component is Radix's Dialog, and runs on `shadcn--dialog`, whose
|
|
30
|
+
// controller this one reaches for when a release means close.
|
|
31
|
+
//
|
|
32
|
+
// Four things vaul does are deliberately not here — snap points, scaling the
|
|
33
|
+
// page behind the drawer, its iOS `position: fixed` workaround, and nested
|
|
34
|
+
// drawers. See features/drawer.md for what each would cost and why.
|
|
35
|
+
export default class extends Controller {
|
|
36
|
+
static targets = [ "content", "overlay" ]
|
|
37
|
+
|
|
38
|
+
connect() {
|
|
39
|
+
this.openedAt = null
|
|
40
|
+
this.scrollPreventedAt = null
|
|
41
|
+
this.reset()
|
|
42
|
+
|
|
43
|
+
// The press that opens the drawer arrives before the animation does, and a
|
|
44
|
+
// drag started during it reads a transform that is still moving.
|
|
45
|
+
this.opened = () => {
|
|
46
|
+
this.openedAt = Date.now()
|
|
47
|
+
this.reset()
|
|
48
|
+
}
|
|
49
|
+
this.element.addEventListener("shadcn--dialog:open", this.opened)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
disconnect() {
|
|
53
|
+
this.element.removeEventListener("shadcn--dialog:open", this.opened)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get direction() {
|
|
57
|
+
return this.hasContentTarget ? this.contentTarget.dataset.vaulDrawerDirection : "bottom"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get vertical() {
|
|
61
|
+
return VERTICAL.has(this.direction)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Which way is "away". Bottom and right close by growing the coordinate, top
|
|
65
|
+
// and left by shrinking it, so one multiplier covers all four.
|
|
66
|
+
get multiplier() {
|
|
67
|
+
return this.direction === "bottom" || this.direction === "right" ? 1 : -1
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
press(event) {
|
|
71
|
+
if (!this.hasContentTarget || event.button !== 0) return
|
|
72
|
+
// Not a drag, and not merely "not worth dragging": the capture below
|
|
73
|
+
// retargets `pointerup` — and the compatibility `mouseup` with it — at the
|
|
74
|
+
// panel, and a click is dispatched at the common ancestor of down and up.
|
|
75
|
+
// Capturing on a button's press therefore means that button never gets a
|
|
76
|
+
// click at all. Typing kept working, because focus follows `pointerdown`,
|
|
77
|
+
// and so did Enter, because no pointer is involved: which is how this
|
|
78
|
+
// arrived — "with the keyboard it submits, with the mouse nothing happens".
|
|
79
|
+
if (event.target.closest?.(CONTROLS)) return
|
|
80
|
+
|
|
81
|
+
this.dragging = true
|
|
82
|
+
this.allowed = false
|
|
83
|
+
this.startedAt = Date.now()
|
|
84
|
+
this.start = this.vertical ? event.pageY : event.pageX
|
|
85
|
+
// Capture, which vaul does not do — and the one place this port knowingly
|
|
86
|
+
// adds something rather than leaving something out.
|
|
87
|
+
//
|
|
88
|
+
// A touch pointer is captured by the browser anyway, so on the device this
|
|
89
|
+
// component is designed for vaul already gets every move and every release.
|
|
90
|
+
// A mouse is not, and vaul binds its handlers to the panel, so with a mouse
|
|
91
|
+
// it loses the gesture the moment the cursor crosses the panel's edge —
|
|
92
|
+
// which a drag *upwards* does immediately, since the rubber band leaves the
|
|
93
|
+
// pointer above the panel by design. Both halves of that were reported here
|
|
94
|
+
// from the gallery: a drag that stopped following, and, before it, a drag
|
|
95
|
+
// released outside that never ended at all because the `pointerup` went to
|
|
96
|
+
// whatever was under the cursor.
|
|
97
|
+
//
|
|
98
|
+
// Capturing makes the mouse behave the way touch already does rather than
|
|
99
|
+
// changing what the component does. The one thing it costs is the target,
|
|
100
|
+
// recovered in `elementUnder` below.
|
|
101
|
+
this.contentTarget.setPointerCapture?.(event.pointerId)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
move(event) {
|
|
105
|
+
if (!this.dragging) return
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
const moved = (this.start - (this.vertical ? event.pageY : event.pageX)) * this.multiplier
|
|
109
|
+
// Positive is *into* the drawer, which is the direction that scrolls.
|
|
110
|
+
const inward = moved > 0
|
|
111
|
+
|
|
112
|
+
if (!this.allowed && !this.shouldDrag(this.elementUnder(event), inward)) return
|
|
113
|
+
this.allowed = true
|
|
114
|
+
|
|
115
|
+
this.hold(this.contentTarget)
|
|
116
|
+
this.hold(this.overlayElement)
|
|
117
|
+
|
|
118
|
+
if (inward) {
|
|
119
|
+
this.translate(Math.min(dampen(moved) * -1, 0) * this.multiplier)
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const distance = Math.abs(moved)
|
|
124
|
+
this.translate(distance * this.multiplier)
|
|
125
|
+
this.fade(1 - distance / this.size)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
release(event) {
|
|
129
|
+
if (!this.dragging) return
|
|
130
|
+
|
|
131
|
+
this.dragging = false
|
|
132
|
+
if (!this.allowed) return
|
|
133
|
+
this.allowed = false
|
|
134
|
+
|
|
135
|
+
const moved = this.start - (this.vertical ? event.pageY : event.pageX)
|
|
136
|
+
const elapsed = Date.now() - this.startedAt
|
|
137
|
+
const velocity = elapsed > 0 ? Math.abs(moved) / elapsed : 0
|
|
138
|
+
const away = moved * this.multiplier < 0
|
|
139
|
+
|
|
140
|
+
if (!away) return this.reset()
|
|
141
|
+
if (velocity > VELOCITY_THRESHOLD) return this.dismiss()
|
|
142
|
+
if (Math.abs(moved) >= this.size * CLOSE_THRESHOLD) return this.dismiss()
|
|
143
|
+
|
|
144
|
+
this.reset()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// vaul's `shouldDrag`, minus the snap-point branches. It is what keeps a
|
|
148
|
+
// scrollable drawer usable: a press that means "scroll the list" must not
|
|
149
|
+
// also mean "throw the drawer away".
|
|
150
|
+
// What the pointer is really over. Capture retargets every move at the panel,
|
|
151
|
+
// and this is the one thing `shouldDrag` cannot do without: it climbs from
|
|
152
|
+
// there to find the scroller that owns the gesture, and a captured event says
|
|
153
|
+
// "the panel" every time.
|
|
154
|
+
elementUnder(event) {
|
|
155
|
+
return document.elementFromPoint(event.clientX, event.clientY) || event.target
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
shouldDrag(target, inward) {
|
|
159
|
+
let element = target
|
|
160
|
+
|
|
161
|
+
if (element.tagName === "SELECT") return false
|
|
162
|
+
if (element.closest("[data-vaul-no-drag]")) return false
|
|
163
|
+
|
|
164
|
+
// Sideways drawers have no scroll axis to compete with.
|
|
165
|
+
if (!this.vertical) return true
|
|
166
|
+
|
|
167
|
+
if (this.openedAt && Date.now() - this.openedAt < OPEN_GRACE) return false
|
|
168
|
+
|
|
169
|
+
// Already part-way out: keep dragging whatever is underneath.
|
|
170
|
+
if (this.translated !== 0) return true
|
|
171
|
+
|
|
172
|
+
if (window.getSelection()?.toString()) return false
|
|
173
|
+
|
|
174
|
+
if (this.scrollPreventedAt && Date.now() - this.scrollPreventedAt < SCROLL_LOCK_TIMEOUT) {
|
|
175
|
+
this.scrollPreventedAt = Date.now()
|
|
176
|
+
return false
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (inward) {
|
|
180
|
+
this.scrollPreventedAt = Date.now()
|
|
181
|
+
return false
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Climb to the first scrollable ancestor. One scrolled away from its top
|
|
185
|
+
// owns the gesture; the drawer itself is where the climb stops.
|
|
186
|
+
while (element && element !== this.element) {
|
|
187
|
+
if (element.scrollHeight > element.clientHeight) {
|
|
188
|
+
if (element.scrollTop !== 0) {
|
|
189
|
+
this.scrollPreventedAt = Date.now()
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (element.getAttribute("role") === "dialog") return true
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
element = element.parentElement
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// How far the drawer has to travel to be gone, along the axis it moves on.
|
|
203
|
+
get size() {
|
|
204
|
+
const box = this.contentTarget.getBoundingClientRect()
|
|
205
|
+
|
|
206
|
+
return this.vertical
|
|
207
|
+
? Math.min(box.height, window.innerHeight)
|
|
208
|
+
: Math.min(box.width, window.innerWidth)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Read back rather than remembered: a `turbo:morph` or a second press can
|
|
212
|
+
// arrive between two drags, and the element is the only thing that knows.
|
|
213
|
+
get translated() {
|
|
214
|
+
const matrix = new DOMMatrixReadOnly(getComputedStyle(this.contentTarget).transform)
|
|
215
|
+
|
|
216
|
+
return this.vertical ? matrix.m42 : matrix.m41
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
get overlayElement() {
|
|
220
|
+
return this.hasOverlayTarget ? this.overlayTarget : null
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
translate(value) {
|
|
224
|
+
this.contentTarget.style.transform = this.vertical
|
|
225
|
+
? `translate3d(0, ${value}px, 0)`
|
|
226
|
+
: `translate3d(${value}px, 0, 0)`
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
fade(opacity) {
|
|
230
|
+
if (this.overlayElement) this.overlayElement.style.opacity = String(opacity)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// The transition has to be off *while* a finger is on the drawer, or every
|
|
234
|
+
// frame chases the last one and the drawer lags behind the pointer.
|
|
235
|
+
hold(element) {
|
|
236
|
+
if (element) element.style.transition = "none"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
reset() {
|
|
240
|
+
for (const element of [ this.hasContentTarget && this.contentTarget, this.overlayElement ]) {
|
|
241
|
+
if (!element) continue
|
|
242
|
+
|
|
243
|
+
element.style.transform = ""
|
|
244
|
+
element.style.opacity = ""
|
|
245
|
+
element.style.transition = ""
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// The inline styles stay, which is vaul's own shape (`closeDrawer` cancels the
|
|
250
|
+
// drag and does not touch the transform, index.tsx:536-549). The exit is a CSS
|
|
251
|
+
// animation with no `from`, so it starts at whatever the element currently
|
|
252
|
+
// reads — which is exactly where the finger left the panel. Clearing first
|
|
253
|
+
// sent the panel back to full height for a frame and slid it away from there,
|
|
254
|
+
// which is what a person sees as it springing up before it goes.
|
|
255
|
+
//
|
|
256
|
+
// They are cleared on the way back *in* instead, so the next open is square
|
|
257
|
+
// rather than already pushed however far the last drag pushed it.
|
|
258
|
+
dismiss() {
|
|
259
|
+
this.dialog?.close()
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
get dialog() {
|
|
263
|
+
return this.application.getControllerForElementAndIdentifier(this.element, "shadcn--dialog")
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { uniqueId } from "shadcn/id"
|
|
3
|
+
import { FloatingLayer } from "shadcn/floating"
|
|
4
|
+
import { Typeahead } from "shadcn/typeahead"
|
|
5
|
+
|
|
6
|
+
// Radix's own: 100ms before a hovered sub-trigger opens (menu.tsx:1123), and
|
|
7
|
+
// 300ms of grace after the pointer leaves it (menu.tsx:1157-1160).
|
|
8
|
+
const SUB_OPEN_DELAY = 100
|
|
9
|
+
const SUB_GRACE_DELAY = 300
|
|
10
|
+
|
|
11
|
+
// Radix's DropdownMenu: a `role="menu"` layer with the ARIA menu keyboard
|
|
12
|
+
// pattern — arrow keys move a `data-highlighted` cursor, typing jumps to a
|
|
13
|
+
// matching item, Escape closes and returns focus to the trigger.
|
|
14
|
+
export default class extends Controller {
|
|
15
|
+
static targets = [ "trigger", "content", "item" ]
|
|
16
|
+
static values = {
|
|
17
|
+
open: Boolean,
|
|
18
|
+
// The `--radix-*` custom properties the content's classes read. The context
|
|
19
|
+
// menu is this same controller with a different prefix and a different way
|
|
20
|
+
// in — its markup, its keyboard and its submenus are the dropdown's, which
|
|
21
|
+
// is why it has no controller of its own. The Sheet reuses the dialog's for
|
|
22
|
+
// the same reason.
|
|
23
|
+
prefix: { type: String, default: "dropdown-menu" },
|
|
24
|
+
side: { type: String, default: "bottom" },
|
|
25
|
+
align: { type: String, default: "start" },
|
|
26
|
+
sideOffset: { type: Number, default: 4 },
|
|
27
|
+
alignOffset: { type: Number, default: 0 },
|
|
28
|
+
loop: Boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
connect() {
|
|
32
|
+
if (!this.hasContentTarget || !this.hasTriggerTarget) return
|
|
33
|
+
|
|
34
|
+
this.typeahead = new Typeahead()
|
|
35
|
+
|
|
36
|
+
this.contentTarget.hidden = true
|
|
37
|
+
this.contentTarget.id ||= uniqueId("shadcn-dropdown")
|
|
38
|
+
|
|
39
|
+
// The id is generated here, so the server has nothing to point this at.
|
|
40
|
+
this.triggerTarget.setAttribute("aria-controls", this.contentTarget.id)
|
|
41
|
+
this.triggerTarget.dataset.state = "closed"
|
|
42
|
+
|
|
43
|
+
this.layer = new FloatingLayer({
|
|
44
|
+
trigger: this.triggerTarget,
|
|
45
|
+
content: this.contentTarget,
|
|
46
|
+
prefix: this.prefixValue,
|
|
47
|
+
side: this.sideValue,
|
|
48
|
+
align: this.alignValue,
|
|
49
|
+
sideOffset: this.sideOffsetValue,
|
|
50
|
+
alignOffset: this.alignOffsetValue,
|
|
51
|
+
onOpen: () => {
|
|
52
|
+
this.triggerTarget.setAttribute("aria-expanded", "true")
|
|
53
|
+
this.contentTarget.focus({ preventScroll: true })
|
|
54
|
+
},
|
|
55
|
+
onClose: () => {
|
|
56
|
+
this.triggerTarget.setAttribute("aria-expanded", "false")
|
|
57
|
+
this.clearHighlight()
|
|
58
|
+
// Radix's menu clears the buffer on blur rather than on close
|
|
59
|
+
// (vendor/radix/ui/menu.tsx:585-590). Closing is where the focus this
|
|
60
|
+
// layer owns actually leaves — nothing here listens for focusout, so a
|
|
61
|
+
// menu that lost focus *without* closing would keep its buffer, which
|
|
62
|
+
// Radix's would not. No path in this gem does that: Tab, Escape and an
|
|
63
|
+
// outside click all close first.
|
|
64
|
+
this.typeahead.reset()
|
|
65
|
+
this.triggerTarget.focus({ preventScroll: true })
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
if (this.openValue) this.layer.show()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
disconnect() {
|
|
73
|
+
this.cancelSubTimers()
|
|
74
|
+
if (this.layer) this.layer.destroy()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
toggle() {
|
|
78
|
+
this.layer.toggle()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// A right-click opens the menu *at the pointer*, so the layer is measured
|
|
82
|
+
// against a point rather than against the element that was pressed. Radix
|
|
83
|
+
// does the same with a virtual element; `popper.js` only ever calls
|
|
84
|
+
// `getBoundingClientRect`, so a zero-sized rect at the coordinates is the
|
|
85
|
+
// whole of it.
|
|
86
|
+
openAtPointer(event) {
|
|
87
|
+
event.preventDefault()
|
|
88
|
+
|
|
89
|
+
const { clientX: x, clientY: y } = event
|
|
90
|
+
|
|
91
|
+
this.layer.anchor = {
|
|
92
|
+
getBoundingClientRect: () => ({
|
|
93
|
+
top: y, bottom: y, left: x, right: x, width: 0, height: 0, x, y
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.layer.hide()
|
|
98
|
+
this.layer.show()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Submenus open on hover, like Radix's SubTrigger — after 100ms, so crossing
|
|
102
|
+
// a trigger on the way somewhere else does not open it (menu.tsx:1123).
|
|
103
|
+
open() {
|
|
104
|
+
this.cancelSubTimers()
|
|
105
|
+
this.subTimer = setTimeout(() => this.layer.show(), SUB_OPEN_DELAY)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
close() {
|
|
109
|
+
this.cancelSubTimers()
|
|
110
|
+
this.layer.hide()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Leaving the trigger, or the panel, starts a close the other one can cancel
|
|
114
|
+
// by being arrived at. Without it a submenu opened by hovering stays open for
|
|
115
|
+
// as long as the menu does, because nothing else was ever going to shut it.
|
|
116
|
+
//
|
|
117
|
+
// Radix grants the same grace but shapes it: a polygon from the exit point to
|
|
118
|
+
// the panel's edges, honoured only while the pointer is *moving toward* it
|
|
119
|
+
// (menu.tsx:1136-1160). This is the time half without the direction half —
|
|
120
|
+
// more forgiving, never less. See features/README.md.
|
|
121
|
+
closeLater() {
|
|
122
|
+
this.cancelSubTimers()
|
|
123
|
+
this.subTimer = setTimeout(() => this.layer.hide(), SUB_GRACE_DELAY)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
cancelSubTimers() {
|
|
127
|
+
if (this.subTimer) clearTimeout(this.subTimer)
|
|
128
|
+
this.subTimer = null
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Opening with ArrowDown/ArrowUp lands on the first/last item, like Radix.
|
|
132
|
+
triggerKeydown(event) {
|
|
133
|
+
if (![ "ArrowDown", "ArrowUp", "Enter", " " ].includes(event.key)) return
|
|
134
|
+
|
|
135
|
+
event.preventDefault()
|
|
136
|
+
this.layer.show()
|
|
137
|
+
|
|
138
|
+
const items = this.enabledItems
|
|
139
|
+
if (items.length === 0) return
|
|
140
|
+
this.highlight(event.key === "ArrowUp" ? items[items.length - 1] : items[0])
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
contentKeydown(event) {
|
|
144
|
+
const items = this.enabledItems
|
|
145
|
+
const current = items.indexOf(this.highlighted)
|
|
146
|
+
|
|
147
|
+
switch (event.key) {
|
|
148
|
+
// Clamp unless asked to wrap, which is how RovingFocusGroup branches:
|
|
149
|
+
// `context.loop ? wrapArray(...) : candidateNodes.slice(currentIndex + 1)`
|
|
150
|
+
// (vendor/radix/ui/roving-focus-group.tsx:324-326) — the slice is empty
|
|
151
|
+
// at the end, so focus stays put. `loop` is opt-in in Radix's menu too
|
|
152
|
+
// (vendor/radix/ui/menu.tsx:387), and vendor/shadcn/ui/dropdown-menu.tsx
|
|
153
|
+
// never passes it, so `false` is the shadcn default rather than a choice
|
|
154
|
+
// made here.
|
|
155
|
+
case "ArrowDown":
|
|
156
|
+
event.preventDefault()
|
|
157
|
+
this.highlight(this.step(items, current, 1))
|
|
158
|
+
return
|
|
159
|
+
// With nothing highlighted yet — which is where a click-open leaves the
|
|
160
|
+
// menu — ArrowUp enters at the *end*: Radix lists it in `LAST_KEYS` and
|
|
161
|
+
// reverses the candidates before `focusFirst`
|
|
162
|
+
// (vendor/radix/ui/menu.tsx:576-583).
|
|
163
|
+
case "ArrowUp":
|
|
164
|
+
event.preventDefault()
|
|
165
|
+
this.highlight(current === -1 ? items[items.length - 1] : this.step(items, current, -1))
|
|
166
|
+
return
|
|
167
|
+
case "Home":
|
|
168
|
+
event.preventDefault()
|
|
169
|
+
this.highlight(items[0])
|
|
170
|
+
return
|
|
171
|
+
case "End":
|
|
172
|
+
event.preventDefault()
|
|
173
|
+
this.highlight(items[items.length - 1])
|
|
174
|
+
return
|
|
175
|
+
case "Enter":
|
|
176
|
+
case " ":
|
|
177
|
+
if (!this.highlighted) return
|
|
178
|
+
event.preventDefault()
|
|
179
|
+
this.highlighted.click()
|
|
180
|
+
return
|
|
181
|
+
case "Tab":
|
|
182
|
+
this.layer.hide()
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (event.key.length === 1 && !event.metaKey && !event.ctrlKey) {
|
|
187
|
+
const match = this.typeahead.search(event.key, this.enabledItems, this.highlighted)
|
|
188
|
+
if (match) this.highlight(match)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
pointerenter(event) {
|
|
193
|
+
this.highlight(event.currentTarget)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
select(event) {
|
|
197
|
+
const item = event.currentTarget
|
|
198
|
+
if (item.dataset.disabled !== undefined) return
|
|
199
|
+
|
|
200
|
+
if (item.getAttribute("role") === "menuitemcheckbox") {
|
|
201
|
+
const checked = item.getAttribute("aria-checked") === "true"
|
|
202
|
+
this.setChecked(item, !checked)
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (item.getAttribute("role") === "menuitemradio") {
|
|
207
|
+
this.radioSiblings(item).forEach((sibling) => this.setChecked(sibling, sibling === item))
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.layer.hide()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// --- helpers -------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
get enabledItems() {
|
|
217
|
+
return this.itemTargets.filter((item) => item.dataset.disabled === undefined)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// One step along `items` from `current`, wrapping only when `loop` is set.
|
|
221
|
+
// With nothing highlighted `current` is -1, so a forward step lands on the
|
|
222
|
+
// first item either way.
|
|
223
|
+
step(items, current, delta) {
|
|
224
|
+
const next = current + delta
|
|
225
|
+
return this.loopValue
|
|
226
|
+
? items[(next + items.length) % items.length]
|
|
227
|
+
: items[Math.min(Math.max(next, 0), items.length - 1)]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
get highlighted() {
|
|
231
|
+
return this.itemTargets.find((item) => item.dataset.highlighted !== undefined) || null
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
highlight(item) {
|
|
235
|
+
this.clearHighlight()
|
|
236
|
+
if (!item) return
|
|
237
|
+
|
|
238
|
+
item.dataset.highlighted = ""
|
|
239
|
+
item.focus({ preventScroll: true })
|
|
240
|
+
item.scrollIntoView({ block: "nearest" })
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
clearHighlight() {
|
|
244
|
+
this.itemTargets.forEach((item) => delete item.dataset.highlighted)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
setChecked(item, checked) {
|
|
248
|
+
item.setAttribute("aria-checked", String(checked))
|
|
249
|
+
item.dataset.state = checked ? "checked" : "unchecked"
|
|
250
|
+
|
|
251
|
+
const indicator = item.querySelector("[data-slot$='-item-indicator']")
|
|
252
|
+
if (indicator) indicator.hidden = !checked
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
radioSiblings(item) {
|
|
256
|
+
const group = item.closest("[data-slot='dropdown-menu-radio-group']") || this.contentTarget
|
|
257
|
+
return Array.from(group.querySelectorAll("[role='menuitemradio']"))
|
|
258
|
+
}
|
|
259
|
+
}
|