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,281 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { ExitQueue } from "shadcn/animation"
|
|
3
|
+
|
|
4
|
+
// sonner's own numbers, read from its source: four seconds of life, 200ms to
|
|
5
|
+
// leave. The lifetime is a value so a caller can change it; the exit is not,
|
|
6
|
+
// because it has to agree with the classes on the toast.
|
|
7
|
+
const DEFAULT_DURATION = 4000
|
|
8
|
+
|
|
9
|
+
// sonner's, read from its source: 14px between toasts.
|
|
10
|
+
const GAP = 14
|
|
11
|
+
|
|
12
|
+
// The toaster is a *list* and this is its clock. Toasts arrive three ways —
|
|
13
|
+
// rendered with the page from a flash, appended by a Turbo Stream, or asked for
|
|
14
|
+
// in JavaScript — and this treats all three the same: anything that appears in
|
|
15
|
+
// the list starts its own timer and leaves when it runs out.
|
|
16
|
+
//
|
|
17
|
+
// Which is why there is a MutationObserver rather than a method every route has
|
|
18
|
+
// to remember to call. A `turbo_stream.append` knows nothing about this
|
|
19
|
+
// controller and should not have to.
|
|
20
|
+
export default class extends Controller {
|
|
21
|
+
static targets = [ "list", "toast", "template" ]
|
|
22
|
+
static values = { duration: { type: Number, default: DEFAULT_DURATION } }
|
|
23
|
+
|
|
24
|
+
connect() {
|
|
25
|
+
this.timers = new Map()
|
|
26
|
+
this.exits = new ExitQueue()
|
|
27
|
+
this.hovering = false
|
|
28
|
+
this.focused = false
|
|
29
|
+
this.expanded = false
|
|
30
|
+
this.floor = 0
|
|
31
|
+
|
|
32
|
+
this.watch = new MutationObserver((records) => {
|
|
33
|
+
for (const record of records) {
|
|
34
|
+
for (const node of record.addedNodes) {
|
|
35
|
+
if (node.nodeType === Node.ELEMENT_NODE && node.dataset.slot === "toast") this.start(node)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.place()
|
|
40
|
+
})
|
|
41
|
+
this.watch.observe(this.listTarget, { childList: true })
|
|
42
|
+
|
|
43
|
+
for (const toast of this.toastElements) this.start(toast)
|
|
44
|
+
|
|
45
|
+
// A toast's own height decides where the one behind it sits, and a height
|
|
46
|
+
// changes when the window narrows and a line wraps.
|
|
47
|
+
this.resizes = new ResizeObserver(() => this.place())
|
|
48
|
+
this.resizes.observe(this.listTarget)
|
|
49
|
+
|
|
50
|
+
this.place()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
disconnect() {
|
|
54
|
+
this.watch.disconnect()
|
|
55
|
+
this.resizes.disconnect()
|
|
56
|
+
for (const timer of this.timers.values()) clearTimeout(timer)
|
|
57
|
+
this.timers.clear()
|
|
58
|
+
this.exits.flushAll()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// `document.dispatchEvent(new CustomEvent("shadcn--toast", { detail: … }))`.
|
|
62
|
+
// The detail is the component's own arguments, so the two routes read alike.
|
|
63
|
+
raise(event) {
|
|
64
|
+
const { title, description, variant = "default", duration } = event.detail ?? {}
|
|
65
|
+
// One template per variant: a variant carries an icon or does not, and an
|
|
66
|
+
// element that was never rendered cannot be brought back by an attribute.
|
|
67
|
+
const template = this.templateTargets.find((t) => t.dataset.variant === variant) ||
|
|
68
|
+
this.templateTargets.find((t) => t.dataset.variant === "default")
|
|
69
|
+
if (!template) return
|
|
70
|
+
|
|
71
|
+
const toast = template.content.firstElementChild.cloneNode(true)
|
|
72
|
+
// `if (duration)` would drop a 0, which is the one value that means
|
|
73
|
+
// something here: no clock. The same slip twice in one file.
|
|
74
|
+
if (duration !== undefined && duration !== null) toast.dataset.duration = duration
|
|
75
|
+
this.fill(toast, "toast-title", title)
|
|
76
|
+
this.fill(toast, "toast-description", description)
|
|
77
|
+
|
|
78
|
+
this.listTarget.appendChild(toast)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
dismiss(event) {
|
|
82
|
+
this.close(event.currentTarget.closest("[data-slot=toast]"))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Hovering does two things, and they are the same gesture: it stops every
|
|
86
|
+
// clock, and it opens the stack. On the list rather than on each toast — a
|
|
87
|
+
// pointer crossing from one to another must not restart the others, and must
|
|
88
|
+
// not collapse the stack under itself on the way.
|
|
89
|
+
//
|
|
90
|
+
// A pointer and a keyboard can hold it open at the same time, and they are
|
|
91
|
+
// tracked apart rather than as one flag: a `focusout` while the pointer is
|
|
92
|
+
// still on the stack is not the stack being let go.
|
|
93
|
+
hold(event) {
|
|
94
|
+
if (event?.type === "focusin") this.focused = true
|
|
95
|
+
else this.hovering = true
|
|
96
|
+
|
|
97
|
+
this.settle()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Only the half the event is about. Closing a toast takes away the button the
|
|
101
|
+
// focus was on, and the browser reports that as focus leaving the stack — so
|
|
102
|
+
// a `focusout` that clears both would shut a stack the pointer is still on,
|
|
103
|
+
// which is what was reported. Focus moving *within* the stack needs nothing
|
|
104
|
+
// here: the `focusin` that follows it arrives in the same tick.
|
|
105
|
+
release(event) {
|
|
106
|
+
if (event?.type === "focusout") this.focused = false
|
|
107
|
+
else this.hovering = false
|
|
108
|
+
|
|
109
|
+
this.settle()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Open while either a pointer or the keyboard is on it; counting down only
|
|
113
|
+
// once neither is.
|
|
114
|
+
settle() {
|
|
115
|
+
this.expanded = this.hovering || this.focused
|
|
116
|
+
|
|
117
|
+
for (const timer of this.timers.values()) clearTimeout(timer)
|
|
118
|
+
this.timers.clear()
|
|
119
|
+
|
|
120
|
+
this.place()
|
|
121
|
+
|
|
122
|
+
if (this.expanded) return
|
|
123
|
+
|
|
124
|
+
for (const toast of this.toastElements) this.start(toast)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// A toast on its way out is not one of these any more. It keeps the transform
|
|
128
|
+
// it had and fades where it stands, while the ones left take their new places
|
|
129
|
+
// — which is the difference between a stack that scrolls and one that jumps
|
|
130
|
+
// once the element finally goes.
|
|
131
|
+
get toastElements() {
|
|
132
|
+
return [ ...this.listTarget.querySelectorAll("[data-slot=toast]:not([data-state=closed])") ]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
get lift() {
|
|
136
|
+
return this.element.dataset.position?.startsWith("top") ? 1 : -1
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Where every toast sits, collapsed and expanded, and how tall the box a
|
|
140
|
+
// pointer has to be inside is.
|
|
141
|
+
//
|
|
142
|
+
// sonner's arrangement, read from its stylesheet: collapsed, the newest is at
|
|
143
|
+
// the front at its own height and each one behind it is lifted by a gap,
|
|
144
|
+
// scaled down a twentieth per place, and has its content faded out — so a
|
|
145
|
+
// stack reads as one toast with others behind it. Expanded, each returns to
|
|
146
|
+
// its own height and is lifted past the ones in front of it.
|
|
147
|
+
place() {
|
|
148
|
+
const toasts = this.toastElements
|
|
149
|
+
if (toasts.length === 0) {
|
|
150
|
+
this.floor = 0
|
|
151
|
+
this.listTarget.style.height = "0px"
|
|
152
|
+
this.listTarget.style.pointerEvents = "none"
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Front first: the newest is nearest the edge, and for a stack anchored at
|
|
157
|
+
// the bottom that is the last one in the list.
|
|
158
|
+
const front = this.lift > 0 ? toasts : [ ...toasts ].reverse()
|
|
159
|
+
const heights = front.map((toast) => this.heightOf(toast))
|
|
160
|
+
const frontHeight = heights[0]
|
|
161
|
+
const edge = this.lift > 0 ? "top" : "bottom"
|
|
162
|
+
let stacked = 0
|
|
163
|
+
let reach = 0
|
|
164
|
+
|
|
165
|
+
front.forEach((toast, index) => {
|
|
166
|
+
const behind = index > 0
|
|
167
|
+
const offset = this.expanded ? stacked + GAP * index : GAP * index
|
|
168
|
+
const scale = this.expanded || !behind ? 1 : 1 - index * 0.05
|
|
169
|
+
const height = this.expanded || !behind ? heights[index] : frontHeight
|
|
170
|
+
|
|
171
|
+
toast.style.zIndex = String(front.length - index)
|
|
172
|
+
toast.style.transform = `translateY(${this.lift * offset}px) scale(${scale})`
|
|
173
|
+
// Scaled from the edge it is anchored to, not from its middle: a toast
|
|
174
|
+
// scaled about its centre grows *upwards* as well as inwards, and reaches
|
|
175
|
+
// past the box it is meant to be inside.
|
|
176
|
+
toast.style.transformOrigin = `${edge} center`
|
|
177
|
+
toast.style.height = this.expanded || !behind ? "" : `${frontHeight}px`
|
|
178
|
+
toast.style.opacity = behind && index > 2 && !this.expanded ? "0" : "1"
|
|
179
|
+
toast.style[edge] = "0px"
|
|
180
|
+
|
|
181
|
+
// Only the front one is readable while the stack is closed; the rest are
|
|
182
|
+
// shapes behind it, which is what makes a stack look like one toast.
|
|
183
|
+
for (const child of toast.children) {
|
|
184
|
+
child.style.opacity = behind && !this.expanded ? "0" : "1"
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
stacked += heights[index]
|
|
188
|
+
reach = Math.max(reach, offset + height * scale)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
// The box has to hold every toast, because it is the box a pointer must stay
|
|
192
|
+
// inside for the stack to stay open. Computed from how far the toasts
|
|
193
|
+
// actually reach rather than from a sum that looked right.
|
|
194
|
+
this.listTarget.style.height = `${this.box(Math.round(reach))}px`
|
|
195
|
+
|
|
196
|
+
// And while the stack is open the box itself has to take the pointer.
|
|
197
|
+
//
|
|
198
|
+
// The region is `pointer-events: none` so the page underneath stays usable
|
|
199
|
+
// around a toast, and each toast turns them back on for itself — which
|
|
200
|
+
// leaves the gaps *between* toasts as holes. A pointer crossing one
|
|
201
|
+
// hit-tests straight through to the page, leaves the list, and the stack
|
|
202
|
+
// shuts and reopens as it lands on the next one: the flicker that was
|
|
203
|
+
// reported. Closed, the holes are wanted; open, they are the reason the
|
|
204
|
+
// stack keeps closing under the pointer that opened it.
|
|
205
|
+
this.listTarget.style.pointerEvents = this.expanded ? "auto" : "none"
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// How tall the box is, which is not always how far the toasts reach: while the
|
|
209
|
+
// stack is open it may grow and must not shrink.
|
|
210
|
+
//
|
|
211
|
+
// The box is anchored at its edge, so a toast going away pulls the far end in
|
|
212
|
+
// by its own height and a gap — past the pointer that is holding the stack
|
|
213
|
+
// open, if that pointer was on the toast that went. The stack then shuts under
|
|
214
|
+
// the hand that opened it, which is what was reported. It is let back down on
|
|
215
|
+
// the way out, where the pointer has already gone.
|
|
216
|
+
box(reach) {
|
|
217
|
+
if (!this.expanded) {
|
|
218
|
+
this.floor = 0
|
|
219
|
+
return reach
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
this.floor = Math.max(this.floor, reach)
|
|
223
|
+
return this.floor
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Measured with the toast at its own size, and remembered: once it is scaled
|
|
227
|
+
// and clipped to the front one's height, the box no longer knows how tall it
|
|
228
|
+
// wants to be.
|
|
229
|
+
heightOf(toast) {
|
|
230
|
+
if (toast.dataset.height) return Number(toast.dataset.height)
|
|
231
|
+
|
|
232
|
+
const height = Math.round(toast.getBoundingClientRect().height)
|
|
233
|
+
if (height > 0) toast.dataset.height = String(height)
|
|
234
|
+
|
|
235
|
+
return height
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
start(toast) {
|
|
239
|
+
if (this.timers.has(toast)) clearTimeout(this.timers.get(toast))
|
|
240
|
+
|
|
241
|
+
// `||` would read a `0` as "not given" and hand back the default, which is
|
|
242
|
+
// the opposite of what 0 means here: no clock at all.
|
|
243
|
+
const asked = toast.dataset.duration
|
|
244
|
+
const duration = asked === undefined || asked === "" ? this.durationValue : Number(asked)
|
|
245
|
+
// A toast that is still loading has no business disappearing on a clock.
|
|
246
|
+
if (toast.dataset.variant === "loading" || duration <= 0) return
|
|
247
|
+
|
|
248
|
+
this.timers.set(toast, setTimeout(() => this.close(toast), duration))
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
close(toast) {
|
|
252
|
+
if (!toast || toast.dataset.state === "closed") return
|
|
253
|
+
|
|
254
|
+
clearTimeout(this.timers.get(toast))
|
|
255
|
+
this.timers.delete(toast)
|
|
256
|
+
toast.dataset.state = "closed"
|
|
257
|
+
// `place()` wrote an inline opacity on every toast, and an inline style
|
|
258
|
+
// beats the class that fades this one out. Handing the property back is what
|
|
259
|
+
// lets `data-[state=closed]:opacity-0` play at all — and the queue below
|
|
260
|
+
// waits on that animation, so without this the toast is gone in the same
|
|
261
|
+
// tick rather than on its way out.
|
|
262
|
+
toast.style.opacity = ""
|
|
263
|
+
// And the rest close the gap it leaves while it fades, rather than after.
|
|
264
|
+
this.place()
|
|
265
|
+
|
|
266
|
+
// The element waits on its own animation rather than on a number here, so
|
|
267
|
+
// the exit and the classes that draw it cannot drift apart.
|
|
268
|
+
this.exits.defer(toast, () => {
|
|
269
|
+
toast.remove()
|
|
270
|
+
this.place()
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
fill(toast, slot, text) {
|
|
275
|
+
const element = toast.querySelector(`[data-slot=${slot}]`)
|
|
276
|
+
if (!element) return
|
|
277
|
+
|
|
278
|
+
if (text) element.textContent = text
|
|
279
|
+
else element.remove()
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Radix's Toggle: an `aria-pressed` button whose styling keys off
|
|
4
|
+
// `data-state="on" | "off"`.
|
|
5
|
+
export default class extends Controller {
|
|
6
|
+
static values = { pressed: Boolean, disabled: Boolean }
|
|
7
|
+
|
|
8
|
+
connect() {
|
|
9
|
+
this.render()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
toggle() {
|
|
13
|
+
if (this.disabledValue) return
|
|
14
|
+
|
|
15
|
+
this.pressedValue = !this.pressedValue
|
|
16
|
+
this.render()
|
|
17
|
+
this.dispatch("change", { detail: { pressed: this.pressedValue } })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
render() {
|
|
21
|
+
this.element.dataset.state = this.pressedValue ? "on" : "off"
|
|
22
|
+
this.element.setAttribute("aria-pressed", String(this.pressedValue))
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { directionAwareKey, readDirection } from "shadcn/direction"
|
|
3
|
+
|
|
4
|
+
// Radix's ToggleGroup, in both `single` and `multiple` modes, with the roving
|
|
5
|
+
// tabindex its items use.
|
|
6
|
+
export default class extends Controller {
|
|
7
|
+
static targets = [ "item", "input" ]
|
|
8
|
+
static values = { type: { type: String, default: "single" }, value: String }
|
|
9
|
+
|
|
10
|
+
connect() {
|
|
11
|
+
this.render()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get multiple() {
|
|
15
|
+
return this.typeValue === "multiple"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get selected() {
|
|
19
|
+
if (!this.valueValue) return []
|
|
20
|
+
return this.valueValue.split(",").filter(Boolean)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
set selected(values) {
|
|
24
|
+
this.valueValue = values.join(",")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
toggle(event) {
|
|
28
|
+
const item = event.currentTarget
|
|
29
|
+
if (item.disabled) return
|
|
30
|
+
|
|
31
|
+
const value = item.dataset.value || ""
|
|
32
|
+
const current = this.selected
|
|
33
|
+
|
|
34
|
+
if (this.multiple) {
|
|
35
|
+
this.selected = current.includes(value)
|
|
36
|
+
? current.filter((v) => v !== value)
|
|
37
|
+
: current.concat(value)
|
|
38
|
+
} else {
|
|
39
|
+
this.selected = current.includes(value) ? [] : [ value ]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.render()
|
|
43
|
+
this.dispatch("change", { detail: { value: this.valueValue } })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
keydown(event) {
|
|
47
|
+
const key = directionAwareKey(event.key, readDirection(this.element))
|
|
48
|
+
const step = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[key]
|
|
49
|
+
if (!step) return
|
|
50
|
+
|
|
51
|
+
event.preventDefault()
|
|
52
|
+
|
|
53
|
+
const items = this.itemTargets.filter((item) => !item.disabled)
|
|
54
|
+
if (items.length === 0) return
|
|
55
|
+
|
|
56
|
+
const current = items.indexOf(event.currentTarget)
|
|
57
|
+
items[(current + step + items.length) % items.length].focus({ preventScroll: true })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
render() {
|
|
61
|
+
const selected = this.selected
|
|
62
|
+
const enabled = this.itemTargets.filter((item) => !item.disabled)
|
|
63
|
+
|
|
64
|
+
this.itemTargets.forEach((item) => {
|
|
65
|
+
const on = selected.includes(item.dataset.value || "")
|
|
66
|
+
item.dataset.state = on ? "on" : "off"
|
|
67
|
+
item.setAttribute("aria-pressed", String(on))
|
|
68
|
+
item.setAttribute("tabindex", item === (enabled.find((i) => selected.includes(i.dataset.value)) || enabled[0]) ? "0" : "-1")
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
if (this.hasInputTarget) this.inputTarget.value = this.valueValue
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { uniqueId } from "shadcn/id"
|
|
3
|
+
import { FloatingLayer } from "shadcn/floating"
|
|
4
|
+
|
|
5
|
+
// Radix's Tooltip: opens on hover or focus, closes on leave, blur or Escape,
|
|
6
|
+
// and never takes focus itself.
|
|
7
|
+
export default class extends Controller {
|
|
8
|
+
static targets = [ "trigger", "content" ]
|
|
9
|
+
static values = {
|
|
10
|
+
side: { type: String, default: "top" },
|
|
11
|
+
align: { type: String, default: "center" },
|
|
12
|
+
sideOffset: { type: Number, default: 0 },
|
|
13
|
+
delay: { type: Number, default: 0 }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
connect() {
|
|
17
|
+
if (!this.hasContentTarget || !this.hasTriggerTarget) return
|
|
18
|
+
|
|
19
|
+
this.contentTarget.hidden = true
|
|
20
|
+
this.contentTarget.setAttribute("role", "tooltip")
|
|
21
|
+
this.contentTarget.id ||= uniqueId("shadcn-tooltip")
|
|
22
|
+
|
|
23
|
+
this.layer = new FloatingLayer({
|
|
24
|
+
trigger: this.triggerTarget,
|
|
25
|
+
content: this.contentTarget,
|
|
26
|
+
prefix: "tooltip",
|
|
27
|
+
side: this.sideValue,
|
|
28
|
+
align: this.alignValue,
|
|
29
|
+
sideOffset: this.sideOffsetValue,
|
|
30
|
+
onOpen: () => this.triggerTarget.setAttribute("aria-describedby", this.contentTarget.id),
|
|
31
|
+
onClose: () => this.triggerTarget.removeAttribute("aria-describedby")
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
disconnect() {
|
|
36
|
+
this.cancel()
|
|
37
|
+
if (this.layer) this.layer.destroy()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
show() {
|
|
41
|
+
this.cancel()
|
|
42
|
+
if (this.delayValue > 0) {
|
|
43
|
+
this.timer = setTimeout(() => this.layer.show(), this.delayValue)
|
|
44
|
+
} else {
|
|
45
|
+
this.layer.show()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
hide() {
|
|
50
|
+
this.cancel()
|
|
51
|
+
this.layer.hide()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
cancel() {
|
|
55
|
+
if (this.timer) clearTimeout(this.timer)
|
|
56
|
+
this.timer = null
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Reading direction, for the controllers whose arrow keys depend on it.
|
|
2
|
+
//
|
|
3
|
+
// This is the port of shadcn's `direction.tsx`, and the port is a module rather
|
|
4
|
+
// than a component because there is nothing to render: upstream's
|
|
5
|
+
// `DirectionProvider` wraps Radix's, which is a React context and emits no DOM
|
|
6
|
+
// at all (vendor/shadcn/ui/direction.tsx). React needs that context because a
|
|
7
|
+
// component cannot easily read inherited DOM state while rendering. A Stimulus
|
|
8
|
+
// controller has no such problem — the browser has already resolved `dir` for
|
|
9
|
+
// every element by the time one runs.
|
|
10
|
+
//
|
|
11
|
+
// So a host writes `<html dir="rtl">`, or `dir` on any ancestor, and these
|
|
12
|
+
// controllers follow. There is no provider to mount and no value to thread
|
|
13
|
+
// through, which is the whole of what this component becomes here.
|
|
14
|
+
|
|
15
|
+
// `getComputedStyle().direction` rather than walking up looking for a `dir`
|
|
16
|
+
// attribute: it resolves inheritance, the CSS `direction` property, and
|
|
17
|
+
// `dir="auto"` — which has no fixed answer until the browser has looked at the
|
|
18
|
+
// text. Reading the attribute would get the first two wrong and the third
|
|
19
|
+
// impossible.
|
|
20
|
+
export function readDirection(element) {
|
|
21
|
+
if (!element) return "ltr"
|
|
22
|
+
|
|
23
|
+
return window.getComputedStyle(element).direction === "rtl" ? "rtl" : "ltr"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Radix's `getDirectionAwareKey` (vendor/radix/ui/roving-focus-group.tsx:359).
|
|
27
|
+
// Right-to-left swaps the two horizontal arrows and nothing else — `ArrowUp`
|
|
28
|
+
// and `ArrowDown` are unaffected, because reading direction is horizontal and
|
|
29
|
+
// a column is a column either way.
|
|
30
|
+
//
|
|
31
|
+
// Applied *before* a key is mapped to an intent, never after: the point is that
|
|
32
|
+
// in RTL the key labelled Left is the one that means "next", so every caller
|
|
33
|
+
// can keep one map and translate the key on the way in.
|
|
34
|
+
export function directionAwareKey(key, direction) {
|
|
35
|
+
if (direction !== "rtl") return key
|
|
36
|
+
|
|
37
|
+
if (key === "ArrowLeft") return "ArrowRight"
|
|
38
|
+
if (key === "ArrowRight") return "ArrowLeft"
|
|
39
|
+
|
|
40
|
+
return key
|
|
41
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Port of Radix's DismissableLayer.
|
|
2
|
+
//
|
|
3
|
+
// Layers form a stack. Escape only closes the topmost one; a pointer press
|
|
4
|
+
// outside a layer closes it and everything above it. Elements registered as
|
|
5
|
+
// "anchors" (the trigger that opened the layer) do not count as outside, so a
|
|
6
|
+
// click on the trigger toggles rather than close-then-reopen.
|
|
7
|
+
|
|
8
|
+
const layers = []
|
|
9
|
+
let listening = false
|
|
10
|
+
|
|
11
|
+
function topLayer() {
|
|
12
|
+
return layers[layers.length - 1]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isInside(layer, target) {
|
|
16
|
+
if (!target) return false
|
|
17
|
+
if (layer.element.contains(target)) return true
|
|
18
|
+
return layer.anchors.some((anchor) => anchor && anchor.contains(target))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function onPointerDown(event) {
|
|
22
|
+
// Close from the top down, stopping at the first layer that owns the target.
|
|
23
|
+
for (let i = layers.length - 1; i >= 0; i--) {
|
|
24
|
+
const layer = layers[i]
|
|
25
|
+
if (isInside(layer, event.target)) break
|
|
26
|
+
layer.onDismiss({ reason: "pointerdown", originalEvent: event })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function onKeyDown(event) {
|
|
31
|
+
if (event.key !== "Escape") return
|
|
32
|
+
|
|
33
|
+
const layer = topLayer()
|
|
34
|
+
if (!layer) return
|
|
35
|
+
|
|
36
|
+
layer.onDismiss({ reason: "escape", originalEvent: event })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function startListening() {
|
|
40
|
+
if (listening) return
|
|
41
|
+
// Capture phase so a layer closes before the click reaches the page.
|
|
42
|
+
document.addEventListener("pointerdown", onPointerDown, true)
|
|
43
|
+
// Escape listens on the bubble phase, and deliberately does not stop
|
|
44
|
+
// propagation: a capture-phase `stopPropagation` here would swallow Escape
|
|
45
|
+
// for the whole document — including the host application's own handlers —
|
|
46
|
+
// whenever any layer, even a tooltip, happened to be open.
|
|
47
|
+
document.addEventListener("keydown", onKeyDown)
|
|
48
|
+
listening = true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function stopListening() {
|
|
52
|
+
if (!listening) return
|
|
53
|
+
document.removeEventListener("pointerdown", onPointerDown, true)
|
|
54
|
+
document.removeEventListener("keydown", onKeyDown)
|
|
55
|
+
listening = false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function pushLayer({ element, anchors = [], onDismiss }) {
|
|
59
|
+
const layer = { element, anchors: [].concat(anchors), onDismiss }
|
|
60
|
+
layers.push(layer)
|
|
61
|
+
startListening()
|
|
62
|
+
return layer
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function removeLayer(layer) {
|
|
66
|
+
const index = layers.indexOf(layer)
|
|
67
|
+
if (index !== -1) layers.splice(index, 1)
|
|
68
|
+
if (layers.length === 0) stopListening()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function isTopLayer(layer) {
|
|
72
|
+
return topLayer() === layer
|
|
73
|
+
}
|