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,360 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { uniqueId } from "shadcn/id"
|
|
3
|
+
import { FloatingLayer } from "shadcn/floating"
|
|
4
|
+
|
|
5
|
+
// Base UI's Combobox: a text field that filters a listbox and keeps the caret.
|
|
6
|
+
//
|
|
7
|
+
// It is the only family here answerable to Base UI rather than Radix, and the
|
|
8
|
+
// difference is in the names rather than in the behaviour — `data-open` where
|
|
9
|
+
// everything else writes `data-state="open"`, `--anchor-width` where everything
|
|
10
|
+
// else reads `--radix-popper-anchor-width`. The arrangement itself is the
|
|
11
|
+
// searchable select's: virtual focus through `aria-activedescendant`, the caret
|
|
12
|
+
// never leaving the input.
|
|
13
|
+
export default class extends Controller {
|
|
14
|
+
static targets = [ "search", "content", "list", "item", "indicator", "input", "trigger", "clear",
|
|
15
|
+
"chip", "chips", "chipTemplate" ]
|
|
16
|
+
|
|
17
|
+
static values = {
|
|
18
|
+
open: Boolean,
|
|
19
|
+
value: String,
|
|
20
|
+
multiple: Boolean,
|
|
21
|
+
values: Array,
|
|
22
|
+
name: String,
|
|
23
|
+
side: { type: String, default: "bottom" },
|
|
24
|
+
align: { type: String, default: "start" },
|
|
25
|
+
sideOffset: { type: Number, default: 6 },
|
|
26
|
+
alignOffset: { type: Number, default: 0 }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
connect() {
|
|
30
|
+
if (!this.hasContentTarget || !this.hasSearchTarget) return
|
|
31
|
+
|
|
32
|
+
this.contentTarget.id ||= uniqueId("shadcn-combobox-content")
|
|
33
|
+
this.searchTarget.setAttribute("aria-controls", this.contentTarget.id)
|
|
34
|
+
|
|
35
|
+
this.layer = new FloatingLayer({
|
|
36
|
+
trigger: this.searchTarget,
|
|
37
|
+
content: this.contentTarget,
|
|
38
|
+
prefix: "combobox",
|
|
39
|
+
side: this.sideValue,
|
|
40
|
+
align: this.alignValue,
|
|
41
|
+
sideOffset: this.sideOffsetValue,
|
|
42
|
+
alignOffset: this.alignOffsetValue,
|
|
43
|
+
matchAnchorWidth: true,
|
|
44
|
+
// The four unprefixed names Base UI publishes and this component's own
|
|
45
|
+
// classes read: `w-(--anchor-width)`, `max-w-(--available-width)`,
|
|
46
|
+
// `origin-(--transform-origin)`. Asked for by this family and by no
|
|
47
|
+
// other, so a host's page gets them only where a combobox is.
|
|
48
|
+
publishBaseUiVariables: true,
|
|
49
|
+
onOpen: () => this.opened(),
|
|
50
|
+
onClose: () => this.closed()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Kept so it can be put back when the last chip goes. Read once, before
|
|
54
|
+
// anything has had a chance to blank it.
|
|
55
|
+
this.placeholder = this.searchTarget.getAttribute("placeholder") ?? ""
|
|
56
|
+
this.syncPlaceholder()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Upstream's multiple example writes `placeholder={value.length > 0 ? '' :
|
|
60
|
+
// 'e.g. TypeScript'}` — the placeholder belongs to an empty control, and once
|
|
61
|
+
// there are chips they are what the field is saying. There the caller does
|
|
62
|
+
// it, because there the caller re-renders; here the chips come and go under
|
|
63
|
+
// the controller, so the controller owns it.
|
|
64
|
+
//
|
|
65
|
+
// Called from `connect` as well, not only after a change: the server can
|
|
66
|
+
// render a field that already has chips beside it, and it did — the preview
|
|
67
|
+
// starts with two.
|
|
68
|
+
syncPlaceholder() {
|
|
69
|
+
if (!this.multipleValue) return
|
|
70
|
+
|
|
71
|
+
this.searchTarget.setAttribute("placeholder", this.valuesValue.length > 0 ? "" : this.placeholder)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
disconnect() {
|
|
75
|
+
this.layer?.destroy()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
toggle(event) {
|
|
79
|
+
event.preventDefault()
|
|
80
|
+
this.openValue ? this.close() : this.open()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
open() {
|
|
84
|
+
if (this.openValue) return
|
|
85
|
+
|
|
86
|
+
this.openValue = true
|
|
87
|
+
this.layer.show()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
close() {
|
|
91
|
+
if (!this.openValue) return
|
|
92
|
+
|
|
93
|
+
this.openValue = false
|
|
94
|
+
this.layer.hide()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
opened() {
|
|
98
|
+
// Base UI's own state attributes, which are what the panel's classes read.
|
|
99
|
+
this.contentTarget.dataset.open = ""
|
|
100
|
+
delete this.contentTarget.dataset.closed
|
|
101
|
+
this.searchTarget.setAttribute("aria-expanded", "true")
|
|
102
|
+
this.highlight(this.chosenItem || this.visibleItems[0])
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
closed() {
|
|
106
|
+
this.contentTarget.dataset.closed = ""
|
|
107
|
+
delete this.contentTarget.dataset.open
|
|
108
|
+
this.searchTarget.setAttribute("aria-expanded", "false")
|
|
109
|
+
this.searchTarget.removeAttribute("aria-activedescendant")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Base UI's default filter is a contains match, which is also what the
|
|
113
|
+
// searchable select does — a combobox completes a value the caller already
|
|
114
|
+
// ordered, where the palette ranks answers. Same reasoning, opposite ends.
|
|
115
|
+
search() {
|
|
116
|
+
const query = this.searchTarget.value.trim().toLowerCase()
|
|
117
|
+
|
|
118
|
+
for (const item of this.itemTargets) {
|
|
119
|
+
item.hidden = query !== "" && !this.textOf(item).toLowerCase().includes(query)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// `data-empty` on the *content*, because that is what the empty state's
|
|
123
|
+
// own class reads: `group-data-empty/combobox-content:flex`.
|
|
124
|
+
if (this.visibleItems.length === 0) this.contentTarget.dataset.empty = ""
|
|
125
|
+
else delete this.contentTarget.dataset.empty
|
|
126
|
+
|
|
127
|
+
this.open()
|
|
128
|
+
this.highlight(this.visibleItems[0])
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
keydown(event) {
|
|
132
|
+
const keys = {
|
|
133
|
+
ArrowDown: () => (this.openValue ? this.move(1) : this.open()),
|
|
134
|
+
ArrowUp: () => (this.openValue ? this.move(-1) : this.open()),
|
|
135
|
+
Home: () => this.highlight(this.visibleItems[0]),
|
|
136
|
+
End: () => this.highlight(this.visibleItems.at(-1)),
|
|
137
|
+
Enter: () => this.take(this.highlighted),
|
|
138
|
+
Escape: () => this.close()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Backspace on an empty field takes the last chip back off. This one is
|
|
142
|
+
// **ours**: Base UI's documentation does not describe a Backspace
|
|
143
|
+
// behaviour, and Base UI itself is not vendored here, so it could not be
|
|
144
|
+
// checked the way the rest of this family was. It is the convention every
|
|
145
|
+
// token field on the web follows, and it is the only way to undo a chip
|
|
146
|
+
// from the keyboard — the X is a pointer target.
|
|
147
|
+
if (this.multipleValue && event.key === "Backspace" && this.searchTarget.value === "") {
|
|
148
|
+
const last = this.chipTargets.at(-1)
|
|
149
|
+
if (last) {
|
|
150
|
+
event.preventDefault()
|
|
151
|
+
this.drop(last.dataset.value ?? "")
|
|
152
|
+
}
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const act = keys[event.key]
|
|
157
|
+
if (!act) return
|
|
158
|
+
|
|
159
|
+
// Tab is deliberately absent: it leaves the field, which is what Tab does.
|
|
160
|
+
event.preventDefault()
|
|
161
|
+
act()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
hover(event) {
|
|
165
|
+
this.highlight(event.currentTarget)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
choose(event) {
|
|
169
|
+
this.take(event.currentTarget)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
clear(event) {
|
|
173
|
+
event.preventDefault()
|
|
174
|
+
this.searchTarget.value = ""
|
|
175
|
+
|
|
176
|
+
if (this.multipleValue) {
|
|
177
|
+
// Every chip, not just the field: in multiple mode the chosen set *is*
|
|
178
|
+
// what the control holds, and leaving the tokens behind would make Clear
|
|
179
|
+
// look like it had done nothing.
|
|
180
|
+
for (const chip of this.chipTargets) chip.remove()
|
|
181
|
+
this.valuesValue = []
|
|
182
|
+
this.writeInputs()
|
|
183
|
+
this.syncPlaceholder()
|
|
184
|
+
} else {
|
|
185
|
+
this.valueValue = ""
|
|
186
|
+
if (this.hasInputTarget) this.inputTarget.value = ""
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
this.mark()
|
|
190
|
+
this.search()
|
|
191
|
+
this.searchTarget.focus()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// A chip taken back off, by its X.
|
|
195
|
+
remove(event) {
|
|
196
|
+
event.preventDefault()
|
|
197
|
+
const chip = event.currentTarget.closest("[data-slot=combobox-chip]")
|
|
198
|
+
if (!chip) return
|
|
199
|
+
|
|
200
|
+
this.drop(chip.dataset.value ?? "", chip)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
take(item) {
|
|
204
|
+
if (!item || item.dataset.disabled !== undefined) return
|
|
205
|
+
|
|
206
|
+
const value = item.dataset.value ?? ""
|
|
207
|
+
|
|
208
|
+
if (this.multipleValue) return this.toggle_value(value, item)
|
|
209
|
+
|
|
210
|
+
this.valueValue = value
|
|
211
|
+
this.searchTarget.value = item.dataset.label || this.textOf(item)
|
|
212
|
+
if (this.hasInputTarget) this.inputTarget.value = this.valueValue
|
|
213
|
+
|
|
214
|
+
this.mark()
|
|
215
|
+
this.close()
|
|
216
|
+
this.searchTarget.focus()
|
|
217
|
+
this.dispatch("select", { detail: { value: this.valueValue, item } })
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Multiple selection. Taking an option that is already taken puts it back —
|
|
221
|
+
// which is what the tick in the list invites, and what leaves the list and
|
|
222
|
+
// the chips describing the same set. Base UI's documentation does not say
|
|
223
|
+
// either way, and Base UI is not vendored here, so this is **ours**.
|
|
224
|
+
//
|
|
225
|
+
// The panel stays open and the field is emptied, which the documentation's
|
|
226
|
+
// own multiple example does show.
|
|
227
|
+
toggle_value(value, item) {
|
|
228
|
+
if (this.valuesValue.includes(value)) return this.drop(value)
|
|
229
|
+
|
|
230
|
+
this.valuesValue = [ ...this.valuesValue, value ]
|
|
231
|
+
this.addChip(value, item.dataset.label || this.textOf(item))
|
|
232
|
+
this.writeInputs()
|
|
233
|
+
this.syncPlaceholder()
|
|
234
|
+
|
|
235
|
+
this.searchTarget.value = ""
|
|
236
|
+
this.search()
|
|
237
|
+
this.mark()
|
|
238
|
+
this.searchTarget.focus()
|
|
239
|
+
this.dispatch("select", { detail: { value, item } })
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
drop(value, chip = null) {
|
|
243
|
+
const token = chip || this.chipTargets.find((c) => (c.dataset.value ?? "") === value)
|
|
244
|
+
token?.remove()
|
|
245
|
+
|
|
246
|
+
if (this.multipleValue) {
|
|
247
|
+
this.valuesValue = this.valuesValue.filter((v) => v !== value)
|
|
248
|
+
this.writeInputs()
|
|
249
|
+
this.syncPlaceholder()
|
|
250
|
+
this.mark()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
this.dispatch("remove", { detail: { value } })
|
|
254
|
+
this.searchTarget.focus()
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// A chip per chosen value, cloned from the `<template>` the Chips component
|
|
258
|
+
// renders — so the class strings stay in Ruby, where `parity_spec` reads
|
|
259
|
+
// them, instead of being copied into this file.
|
|
260
|
+
addChip(value, label) {
|
|
261
|
+
if (!this.hasChipsTarget || !this.hasChipTemplateTarget) return
|
|
262
|
+
|
|
263
|
+
const chip = this.chipTemplateTarget.content.firstElementChild.cloneNode(true)
|
|
264
|
+
chip.dataset.value = value
|
|
265
|
+
|
|
266
|
+
const remove = chip.querySelector("[data-slot=combobox-chip-remove]")
|
|
267
|
+
if (remove) remove.dataset.value = value
|
|
268
|
+
|
|
269
|
+
// Before the remove button, which the Chip component renders last.
|
|
270
|
+
chip.insertBefore(document.createTextNode(label), remove)
|
|
271
|
+
|
|
272
|
+
// Before the field, not before the `<template>`. The template is the last
|
|
273
|
+
// child, so inserting there put every new chip *after* the input: the ones
|
|
274
|
+
// the server rendered sat to its left and the ones taken since to its
|
|
275
|
+
// right, with the field stranded in the middle. The field is the anchor —
|
|
276
|
+
// upstream's chips box is a row of tokens with the input taking whatever is
|
|
277
|
+
// left of the last line.
|
|
278
|
+
this.chipsTarget.insertBefore(chip, this.searchTarget)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// One hidden input per chosen value, rewritten whenever the set changes. The
|
|
282
|
+
// first input the component rendered carries the empty value that keeps the
|
|
283
|
+
// parameter present when every chip is gone, and is left alone — it has no
|
|
284
|
+
// target, so it is not in `inputTargets` to begin with.
|
|
285
|
+
writeInputs() {
|
|
286
|
+
if (!this.hasNameValue) return
|
|
287
|
+
|
|
288
|
+
for (const input of this.inputTargets) input.remove()
|
|
289
|
+
|
|
290
|
+
for (const value of this.valuesValue) {
|
|
291
|
+
const input = document.createElement("input")
|
|
292
|
+
input.type = "hidden"
|
|
293
|
+
input.name = this.nameValue
|
|
294
|
+
input.value = value
|
|
295
|
+
input.autocomplete = "off"
|
|
296
|
+
// `setAttribute`, not `dataset`: the controller's identifier contains a
|
|
297
|
+
// double dash, and `data-shadcn--combobox-target` has no camelCase form
|
|
298
|
+
// `dataset` round-trips to.
|
|
299
|
+
input.setAttribute("data-shadcn--combobox-target", "input")
|
|
300
|
+
this.element.appendChild(input)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// The tick, which is the only thing that says which ones were taken once the
|
|
305
|
+
// panel is closed and reopened.
|
|
306
|
+
mark() {
|
|
307
|
+
for (const item of this.itemTargets) {
|
|
308
|
+
const value = item.dataset.value ?? ""
|
|
309
|
+
const chosen = this.multipleValue
|
|
310
|
+
? this.valuesValue.includes(value)
|
|
311
|
+
: value === this.valueValue && this.valueValue !== ""
|
|
312
|
+
|
|
313
|
+
item.setAttribute("aria-selected", String(chosen))
|
|
314
|
+
const indicator = item.querySelector("[data-slot=combobox-item-indicator]")
|
|
315
|
+
if (indicator) indicator.hidden = !chosen
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
move(by) {
|
|
320
|
+
const items = this.visibleItems
|
|
321
|
+
if (items.length === 0) return
|
|
322
|
+
|
|
323
|
+
const at = items.indexOf(this.highlighted)
|
|
324
|
+
this.highlight(items[(at + by + items.length) % items.length])
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
highlight(item) {
|
|
328
|
+
for (const other of this.itemTargets) {
|
|
329
|
+
if (other === item) other.dataset.highlighted = ""
|
|
330
|
+
else delete other.dataset.highlighted
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// The Item component gives every server-rendered option an id, so this only
|
|
334
|
+
// catches the ones a host adds later — but that is exactly the case that was
|
|
335
|
+
// broken, and it failed without a sound.
|
|
336
|
+
if (item) {
|
|
337
|
+
item.id ||= uniqueId("shadcn-combobox-item")
|
|
338
|
+
this.searchTarget.setAttribute("aria-activedescendant", item.id)
|
|
339
|
+
}
|
|
340
|
+
else this.searchTarget.removeAttribute("aria-activedescendant")
|
|
341
|
+
|
|
342
|
+
item?.scrollIntoView({ block: "nearest" })
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
get highlighted() {
|
|
346
|
+
return this.itemTargets.find((item) => item.dataset.highlighted !== undefined)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
get chosenItem() {
|
|
350
|
+
return this.itemTargets.find((item) => (item.dataset.value ?? "") === this.valueValue && this.valueValue !== "")
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
get visibleItems() {
|
|
354
|
+
return this.itemTargets.filter((item) => !item.hidden && item.dataset.disabled === undefined)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
textOf(item) {
|
|
358
|
+
return item.dataset.label || item.textContent.trim().replace(/\s+/g, " ")
|
|
359
|
+
}
|
|
360
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { commandScore } from "shadcn/command_score"
|
|
3
|
+
import { uniqueId } from "shadcn/id"
|
|
4
|
+
|
|
5
|
+
// The palette. Items are rendered by the server and this filters, ranks and
|
|
6
|
+
// walks them — which is what `cmdk` does, minus the part where it also owns the
|
|
7
|
+
// markup.
|
|
8
|
+
//
|
|
9
|
+
// Ranking rather than filtering is the difference between this and the
|
|
10
|
+
// searchable select, and it is deliberate: a select filters a list its caller
|
|
11
|
+
// ordered and keeps that order, while a palette answers "what did you mean" and
|
|
12
|
+
// has to put the answer first.
|
|
13
|
+
export default class extends Controller {
|
|
14
|
+
static targets = [ "search", "list", "item", "group", "separator", "empty" ]
|
|
15
|
+
|
|
16
|
+
connect() {
|
|
17
|
+
// The order the server rendered, which is what an empty query goes back to.
|
|
18
|
+
this.order = new Map(this.itemTargets.map((item, index) => [ item, index ]))
|
|
19
|
+
this.select(this.visibleItems[0])
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
search() {
|
|
23
|
+
const query = this.searchTarget.value.trim()
|
|
24
|
+
|
|
25
|
+
for (const item of this.itemTargets) {
|
|
26
|
+
const score = query === "" ? 1 : commandScore(this.textOf(item), query, this.keywordsOf(item))
|
|
27
|
+
|
|
28
|
+
item.dataset.score = score
|
|
29
|
+
item.hidden = score === 0
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.rank(query)
|
|
33
|
+
this.settle()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Within a group first, then the groups themselves by their best item: an
|
|
37
|
+
// answer buried under a heading nobody is looking at is not an answer.
|
|
38
|
+
rank(query) {
|
|
39
|
+
for (const container of this.containers) {
|
|
40
|
+
// `:scope >`, because the list holds the groups as well as any loose
|
|
41
|
+
// items: without it every item in the document matched, and appending
|
|
42
|
+
// them to the list pulled them out of the groups they belonged to.
|
|
43
|
+
const items = [ ...container.querySelectorAll(":scope > [cmdk-item]") ]
|
|
44
|
+
const sorted = query === ""
|
|
45
|
+
? items.sort((a, b) => this.order.get(a) - this.order.get(b))
|
|
46
|
+
: items.sort((a, b) => this.scoreOf(b) - this.scoreOf(a) || this.order.get(a) - this.order.get(b))
|
|
47
|
+
|
|
48
|
+
for (const item of sorted) container.appendChild(item)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (this.groupTargets.length < 2) return
|
|
52
|
+
|
|
53
|
+
const groups = [ ...this.groupTargets ].sort((a, b) =>
|
|
54
|
+
query === "" ? this.order.get(this.firstOf(a)) - this.order.get(this.firstOf(b)) : this.bestOf(b) - this.bestOf(a))
|
|
55
|
+
|
|
56
|
+
for (const group of groups) group.parentElement.appendChild(group)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// What is left showing decides the rest: an empty state, the groups that
|
|
60
|
+
// still have something in them, and where the selection goes.
|
|
61
|
+
settle() {
|
|
62
|
+
for (const group of this.groupTargets) {
|
|
63
|
+
group.hidden = [ ...group.querySelectorAll("[cmdk-item]") ].every((item) => item.hidden)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// A rule between two things is a rule between nothing once one of them has
|
|
67
|
+
// gone.
|
|
68
|
+
for (const separator of this.separatorTargets) {
|
|
69
|
+
separator.hidden = this.groupTargets.length > 0 && this.groupTargets.every((group) => group.hidden)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const matches = this.visibleItems
|
|
73
|
+
// The empty state lives *inside* the list — upstream's own example puts it
|
|
74
|
+
// there — so hiding the list when nothing matches hides the message that
|
|
75
|
+
// says nothing matches. The select hides its list because its empty state
|
|
76
|
+
// is a sibling; this one is not.
|
|
77
|
+
for (const empty of this.emptyTargets) empty.hidden = matches.length > 0
|
|
78
|
+
|
|
79
|
+
this.select(matches.includes(this.selected) ? this.selected : matches[0])
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
keydown(event) {
|
|
83
|
+
const keys = {
|
|
84
|
+
ArrowDown: () => this.move(1),
|
|
85
|
+
ArrowUp: () => this.move(-1),
|
|
86
|
+
Home: () => this.select(this.visibleItems[0]),
|
|
87
|
+
End: () => this.select(this.visibleItems.at(-1)),
|
|
88
|
+
Enter: () => this.choose()
|
|
89
|
+
}
|
|
90
|
+
const move = keys[event.key]
|
|
91
|
+
if (!move) return
|
|
92
|
+
|
|
93
|
+
event.preventDefault()
|
|
94
|
+
move()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The pointer selects what it is over, as cmdk does — so the keyboard and the
|
|
98
|
+
// pointer never disagree about which item Enter would take.
|
|
99
|
+
hover(event) {
|
|
100
|
+
this.select(event.currentTarget)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
choose(event) {
|
|
104
|
+
const item = event?.currentTarget ?? this.selected
|
|
105
|
+
if (!item || item.dataset.disabled === "true") return
|
|
106
|
+
|
|
107
|
+
this.select(item)
|
|
108
|
+
this.dispatch("select", { detail: { value: this.valueOf(item), item } })
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
move(by) {
|
|
112
|
+
const items = this.visibleItems
|
|
113
|
+
if (items.length === 0) return
|
|
114
|
+
|
|
115
|
+
const at = items.indexOf(this.selected)
|
|
116
|
+
// Wrapping, which is cmdk's default (`loop` turns it off there, and the
|
|
117
|
+
// palette is a ring: the first item is one press up from the last).
|
|
118
|
+
this.select(items[(at + by + items.length) % items.length])
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
select(item) {
|
|
122
|
+
for (const other of this.itemTargets) {
|
|
123
|
+
const chosen = other === item
|
|
124
|
+
|
|
125
|
+
other.setAttribute("aria-selected", String(chosen))
|
|
126
|
+
other.dataset.selected = String(chosen)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this.selected = item
|
|
130
|
+
this.publish(item)
|
|
131
|
+
item?.scrollIntoView({ block: "nearest" })
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// The virtual focus: the input keeps the caret and the list is walked by id,
|
|
135
|
+
// which is the same arrangement the searchable select uses.
|
|
136
|
+
publish(item) {
|
|
137
|
+
for (const element of [ this.hasSearchTarget && this.searchTarget, this.hasListTarget && this.listTarget ]) {
|
|
138
|
+
if (!element) continue
|
|
139
|
+
|
|
140
|
+
if (item) {
|
|
141
|
+
// As in the select and the combobox: the id is assigned where it is
|
|
142
|
+
// read, so an item added after `connect` still has one to point at.
|
|
143
|
+
item.id ||= uniqueId("shadcn-command-item")
|
|
144
|
+
element.setAttribute("aria-activedescendant", item.id)
|
|
145
|
+
}
|
|
146
|
+
else element.removeAttribute("aria-activedescendant")
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
get selected() {
|
|
151
|
+
return this._selected
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
set selected(item) {
|
|
155
|
+
this._selected = item
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
get visibleItems() {
|
|
159
|
+
return this.itemTargets.filter((item) => !item.hidden && item.dataset.disabled !== "true" && !this.insideHiddenGroup(item))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
insideHiddenGroup(item) {
|
|
163
|
+
return this.groupTargets.some((group) => group.hidden && group.contains(item))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Where items live: the wrapper inside each group, and the list itself for
|
|
167
|
+
// the ones written outside a group.
|
|
168
|
+
get containers() {
|
|
169
|
+
const groups = this.groupTargets.map((group) => group.querySelector("[cmdk-group-items]") || group)
|
|
170
|
+
|
|
171
|
+
return this.hasListTarget ? [ ...groups, this.listTarget ] : groups
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
firstOf(group) {
|
|
175
|
+
return group.querySelector("[cmdk-item]")
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
bestOf(group) {
|
|
179
|
+
return [ ...group.querySelectorAll("[cmdk-item]") ].reduce((best, item) => Math.max(best, this.scoreOf(item)), 0)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
scoreOf(item) {
|
|
183
|
+
return Number(item.dataset.score ?? 0)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// What is searched: the value a caller gave, otherwise the text a reader
|
|
187
|
+
// sees — cmdk's own rule.
|
|
188
|
+
textOf(item) {
|
|
189
|
+
return item.dataset.value || item.textContent.trim().replace(/\s+/g, " ")
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
keywordsOf(item) {
|
|
193
|
+
return (item.dataset.keywords || "").split(/\s+/).filter(Boolean)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
valueOf(item) {
|
|
197
|
+
return item.dataset.value || this.textOf(item)
|
|
198
|
+
}
|
|
199
|
+
}
|