shadcn-rails 0.1.0
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/.dockerignore +40 -0
- data/CHANGELOG.md +54 -0
- data/CLAUDE.md +463 -0
- data/PROGRESS.md +485 -0
- data/README.md +1483 -0
- data/Rakefile +29 -0
- data/__tests__/controllers/__snapshots__/calendar_controller.test.js.snap +13 -0
- data/__tests__/controllers/__snapshots__/popover_controller.test.js.snap +46 -0
- data/__tests__/controllers/__snapshots__/sheet_controller.test.js.snap +111 -0
- data/__tests__/controllers/__snapshots__/tabs_controller.test.js.snap +27 -0
- data/__tests__/controllers/accordion_controller.test.js +904 -0
- data/__tests__/controllers/calendar_controller.test.js +1370 -0
- data/__tests__/controllers/carousel_controller.test.js +912 -0
- data/__tests__/controllers/checkbox_controller.test.js +454 -0
- data/__tests__/controllers/collapsible_controller.test.js +407 -0
- data/__tests__/controllers/combobox_controller.test.js +966 -0
- data/__tests__/controllers/context_menu_controller.test.js +627 -0
- data/__tests__/controllers/date_picker_controller.test.js +636 -0
- data/__tests__/controllers/dialog_controller.test.js +878 -0
- data/__tests__/controllers/drawer_controller.test.js +995 -0
- data/__tests__/controllers/menubar_controller.test.js +736 -0
- data/__tests__/controllers/navigation_menu_controller.test.js +598 -0
- data/__tests__/controllers/popover_controller.test.js +1007 -0
- data/__tests__/controllers/radio_group_controller.test.js +640 -0
- data/__tests__/controllers/resizable_controller.test.js +680 -0
- data/__tests__/controllers/select_controller.test.js +674 -0
- data/__tests__/controllers/sheet_controller.test.js +986 -0
- data/__tests__/controllers/slider_controller.test.js +1036 -0
- data/__tests__/controllers/switch_controller.test.js +424 -0
- data/__tests__/controllers/tabs_controller.test.js +907 -0
- data/__tests__/controllers/toggle_group_controller.test.js +839 -0
- data/__tests__/controllers/tooltip_controller.test.js +808 -0
- data/__tests__/helpers/stimulus-test-helper.js +203 -0
- data/app/assets/config/manifest.js +1 -0
- data/app/assets/javascripts/shadcn/controllers/accordion_controller.d.ts +53 -0
- data/app/assets/javascripts/shadcn/controllers/accordion_controller.js +140 -0
- data/app/assets/javascripts/shadcn/controllers/avatar_controller.d.ts +22 -0
- data/app/assets/javascripts/shadcn/controllers/avatar_controller.js +26 -0
- data/app/assets/javascripts/shadcn/controllers/calendar_controller.js +592 -0
- data/app/assets/javascripts/shadcn/controllers/carousel_controller.js +263 -0
- data/app/assets/javascripts/shadcn/controllers/checkbox_controller.d.ts +31 -0
- data/app/assets/javascripts/shadcn/controllers/checkbox_controller.js +48 -0
- data/app/assets/javascripts/shadcn/controllers/collapsible_controller.d.ts +43 -0
- data/app/assets/javascripts/shadcn/controllers/collapsible_controller.js +73 -0
- data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +234 -0
- data/app/assets/javascripts/shadcn/controllers/command_controller.js +141 -0
- data/app/assets/javascripts/shadcn/controllers/command_dialog_controller.js +162 -0
- data/app/assets/javascripts/shadcn/controllers/context_menu_controller.js +202 -0
- data/app/assets/javascripts/shadcn/controllers/date_picker_controller.js +282 -0
- data/app/assets/javascripts/shadcn/controllers/dialog_controller.d.ts +67 -0
- data/app/assets/javascripts/shadcn/controllers/dialog_controller.js +187 -0
- data/app/assets/javascripts/shadcn/controllers/drawer_controller.d.ts +58 -0
- data/app/assets/javascripts/shadcn/controllers/drawer_controller.js +112 -0
- data/app/assets/javascripts/shadcn/controllers/dropdown_controller.d.ts +83 -0
- data/app/assets/javascripts/shadcn/controllers/dropdown_controller.js +225 -0
- data/app/assets/javascripts/shadcn/controllers/hover_card_controller.d.ts +59 -0
- data/app/assets/javascripts/shadcn/controllers/hover_card_controller.js +143 -0
- data/app/assets/javascripts/shadcn/controllers/input_otp_controller.d.ts +44 -0
- data/app/assets/javascripts/shadcn/controllers/input_otp_controller.js +206 -0
- data/app/assets/javascripts/shadcn/controllers/menubar_controller.js +323 -0
- data/app/assets/javascripts/shadcn/controllers/navigation_menu_controller.js +251 -0
- data/app/assets/javascripts/shadcn/controllers/popover_controller.d.ts +56 -0
- data/app/assets/javascripts/shadcn/controllers/popover_controller.js +141 -0
- data/app/assets/javascripts/shadcn/controllers/radio_group_controller.d.ts +47 -0
- data/app/assets/javascripts/shadcn/controllers/radio_group_controller.js +108 -0
- data/app/assets/javascripts/shadcn/controllers/resizable_controller.js +272 -0
- data/app/assets/javascripts/shadcn/controllers/scroll_area_controller.d.ts +44 -0
- data/app/assets/javascripts/shadcn/controllers/scroll_area_controller.js +74 -0
- data/app/assets/javascripts/shadcn/controllers/select_controller.d.ts +84 -0
- data/app/assets/javascripts/shadcn/controllers/select_controller.js +222 -0
- data/app/assets/javascripts/shadcn/controllers/sheet_controller.d.ts +60 -0
- data/app/assets/javascripts/shadcn/controllers/sheet_controller.js +151 -0
- data/app/assets/javascripts/shadcn/controllers/sidebar_controller.js +148 -0
- data/app/assets/javascripts/shadcn/controllers/slider_controller.d.ts +102 -0
- data/app/assets/javascripts/shadcn/controllers/slider_controller.js +364 -0
- data/app/assets/javascripts/shadcn/controllers/switch_controller.d.ts +46 -0
- data/app/assets/javascripts/shadcn/controllers/switch_controller.js +78 -0
- data/app/assets/javascripts/shadcn/controllers/tabs_controller.d.ts +51 -0
- data/app/assets/javascripts/shadcn/controllers/tabs_controller.js +126 -0
- data/app/assets/javascripts/shadcn/controllers/toast_controller.d.ts +37 -0
- data/app/assets/javascripts/shadcn/controllers/toast_controller.js +58 -0
- data/app/assets/javascripts/shadcn/controllers/toggle_controller.d.ts +27 -0
- data/app/assets/javascripts/shadcn/controllers/toggle_controller.js +42 -0
- data/app/assets/javascripts/shadcn/controllers/toggle_group_controller.d.ts +44 -0
- data/app/assets/javascripts/shadcn/controllers/toggle_group_controller.js +68 -0
- data/app/assets/javascripts/shadcn/controllers/tooltip_controller.d.ts +56 -0
- data/app/assets/javascripts/shadcn/controllers/tooltip_controller.js +117 -0
- data/app/assets/javascripts/shadcn/index.d.ts +74 -0
- data/app/assets/javascripts/shadcn/index.js +133 -0
- data/app/assets/stylesheets/.keep +0 -0
- data/app/assets/stylesheets/shadcn/base.css +445 -0
- data/app/assets/stylesheets/shadcn/components.css +513 -0
- data/app/assets/stylesheets/shadcn/index.css +18 -0
- data/app/assets/stylesheets/shadcn/themes/gray.css +68 -0
- data/app/assets/stylesheets/shadcn/themes/slate.css +68 -0
- data/app/assets/stylesheets/shadcn/themes/stone.css +68 -0
- data/app/assets/stylesheets/shadcn/themes/zinc.css +68 -0
- data/app/components/shadcn/accordion_component.rb +63 -0
- data/app/components/shadcn/accordion_content_component.rb +29 -0
- data/app/components/shadcn/accordion_item_component.rb +40 -0
- data/app/components/shadcn/accordion_trigger_component.rb +49 -0
- data/app/components/shadcn/alert_component.rb +75 -0
- data/app/components/shadcn/alert_description_component.rb +12 -0
- data/app/components/shadcn/alert_dialog_action_component.rb +24 -0
- data/app/components/shadcn/alert_dialog_cancel_component.rb +24 -0
- data/app/components/shadcn/alert_dialog_component.rb +71 -0
- data/app/components/shadcn/alert_dialog_content_component.rb +57 -0
- data/app/components/shadcn/alert_dialog_description_component.rb +12 -0
- data/app/components/shadcn/alert_dialog_footer_component.rb +19 -0
- data/app/components/shadcn/alert_dialog_header_component.rb +19 -0
- data/app/components/shadcn/alert_dialog_title_component.rb +12 -0
- data/app/components/shadcn/alert_title_component.rb +12 -0
- data/app/components/shadcn/aspect_ratio_component.rb +49 -0
- data/app/components/shadcn/avatar_component.rb +107 -0
- data/app/components/shadcn/avatar_fallback_component.rb +17 -0
- data/app/components/shadcn/badge_component.rb +49 -0
- data/app/components/shadcn/base_component.rb +100 -0
- data/app/components/shadcn/breadcrumb_component.rb +70 -0
- data/app/components/shadcn/breadcrumb_item_component.rb +50 -0
- data/app/components/shadcn/button_component.rb +141 -0
- data/app/components/shadcn/button_group_component.rb +69 -0
- data/app/components/shadcn/calendar_component.rb +337 -0
- data/app/components/shadcn/card_action_component.rb +10 -0
- data/app/components/shadcn/card_component.rb +63 -0
- data/app/components/shadcn/card_content_component.rb +19 -0
- data/app/components/shadcn/card_description_component.rb +12 -0
- data/app/components/shadcn/card_footer_component.rb +12 -0
- data/app/components/shadcn/card_header_component.rb +24 -0
- data/app/components/shadcn/card_title_component.rb +18 -0
- data/app/components/shadcn/carousel_component.rb +275 -0
- data/app/components/shadcn/checkbox_component.rb +103 -0
- data/app/components/shadcn/collapsible_component.rb +66 -0
- data/app/components/shadcn/collapsible_content_component.rb +28 -0
- data/app/components/shadcn/combobox_component.rb +322 -0
- data/app/components/shadcn/command_component.rb +52 -0
- data/app/components/shadcn/command_dialog_component.rb +76 -0
- data/app/components/shadcn/command_empty_component.rb +12 -0
- data/app/components/shadcn/command_group_component.rb +34 -0
- data/app/components/shadcn/command_input_component.rb +59 -0
- data/app/components/shadcn/command_item_component.rb +48 -0
- data/app/components/shadcn/command_list_component.rb +38 -0
- data/app/components/shadcn/command_separator_component.rb +12 -0
- data/app/components/shadcn/command_shortcut_component.rb +12 -0
- data/app/components/shadcn/context_menu_component.rb +64 -0
- data/app/components/shadcn/context_menu_content_component.rb +44 -0
- data/app/components/shadcn/context_menu_item_component.rb +63 -0
- data/app/components/shadcn/context_menu_label_component.rb +18 -0
- data/app/components/shadcn/context_menu_separator_component.rb +12 -0
- data/app/components/shadcn/context_menu_shortcut_component.rb +12 -0
- data/app/components/shadcn/date_picker_component.rb +368 -0
- data/app/components/shadcn/dialog_component.rb +77 -0
- data/app/components/shadcn/dialog_content_component.rb +91 -0
- data/app/components/shadcn/dialog_description_component.rb +12 -0
- data/app/components/shadcn/dialog_footer_component.rb +12 -0
- data/app/components/shadcn/dialog_header_component.rb +19 -0
- data/app/components/shadcn/dialog_title_component.rb +12 -0
- data/app/components/shadcn/drawer_component.rb +72 -0
- data/app/components/shadcn/drawer_content_component.rb +76 -0
- data/app/components/shadcn/drawer_description_component.rb +12 -0
- data/app/components/shadcn/drawer_footer_component.rb +12 -0
- data/app/components/shadcn/drawer_header_component.rb +19 -0
- data/app/components/shadcn/drawer_title_component.rb +12 -0
- data/app/components/shadcn/dropdown_menu_component.rb +75 -0
- data/app/components/shadcn/dropdown_menu_content_component.rb +49 -0
- data/app/components/shadcn/dropdown_menu_group_component.rb +10 -0
- data/app/components/shadcn/dropdown_menu_item_component.rb +63 -0
- data/app/components/shadcn/dropdown_menu_label_component.rb +18 -0
- data/app/components/shadcn/dropdown_menu_separator_component.rb +12 -0
- data/app/components/shadcn/dropdown_menu_shortcut_component.rb +12 -0
- data/app/components/shadcn/empty_component.rb +48 -0
- data/app/components/shadcn/empty_content_component.rb +12 -0
- data/app/components/shadcn/empty_description_component.rb +12 -0
- data/app/components/shadcn/empty_header_component.rb +29 -0
- data/app/components/shadcn/empty_media_component.rb +21 -0
- data/app/components/shadcn/empty_title_component.rb +12 -0
- data/app/components/shadcn/field_component.rb +113 -0
- data/app/components/shadcn/hover_card_component.rb +64 -0
- data/app/components/shadcn/hover_card_content_component.rb +36 -0
- data/app/components/shadcn/input_component.rb +108 -0
- data/app/components/shadcn/input_group_component.rb +70 -0
- data/app/components/shadcn/input_otp_component.rb +183 -0
- data/app/components/shadcn/item_actions_component.rb +12 -0
- data/app/components/shadcn/item_component.rb +98 -0
- data/app/components/shadcn/item_content_component.rb +24 -0
- data/app/components/shadcn/item_description_component.rb +12 -0
- data/app/components/shadcn/item_footer_component.rb +12 -0
- data/app/components/shadcn/item_group_component.rb +24 -0
- data/app/components/shadcn/item_header_component.rb +12 -0
- data/app/components/shadcn/item_media_component.rb +22 -0
- data/app/components/shadcn/item_separator_component.rb +12 -0
- data/app/components/shadcn/item_title_component.rb +12 -0
- data/app/components/shadcn/kbd_component.rb +36 -0
- data/app/components/shadcn/label_component.rb +49 -0
- data/app/components/shadcn/menubar_checkbox_item_component.rb +76 -0
- data/app/components/shadcn/menubar_component.rb +56 -0
- data/app/components/shadcn/menubar_content_component.rb +64 -0
- data/app/components/shadcn/menubar_item_component.rb +65 -0
- data/app/components/shadcn/menubar_label_component.rb +27 -0
- data/app/components/shadcn/menubar_menu_component.rb +34 -0
- data/app/components/shadcn/menubar_radio_group_component.rb +42 -0
- data/app/components/shadcn/menubar_radio_item_component.rb +76 -0
- data/app/components/shadcn/menubar_separator_component.rb +22 -0
- data/app/components/shadcn/menubar_shortcut_component.rb +21 -0
- data/app/components/shadcn/menubar_sub_component.rb +38 -0
- data/app/components/shadcn/menubar_sub_content_component.rb +45 -0
- data/app/components/shadcn/menubar_sub_trigger_component.rb +59 -0
- data/app/components/shadcn/menubar_trigger_component.rb +31 -0
- data/app/components/shadcn/native_select_component.rb +150 -0
- data/app/components/shadcn/navigation_menu_component.rb +76 -0
- data/app/components/shadcn/navigation_menu_content_component.rb +30 -0
- data/app/components/shadcn/navigation_menu_item_component.rb +39 -0
- data/app/components/shadcn/navigation_menu_link_component.rb +38 -0
- data/app/components/shadcn/navigation_menu_list_component.rb +29 -0
- data/app/components/shadcn/navigation_menu_trigger_component.rb +59 -0
- data/app/components/shadcn/pagination_component.rb +195 -0
- data/app/components/shadcn/pagination_content_component.rb +47 -0
- data/app/components/shadcn/pagination_ellipsis_component.rb +30 -0
- data/app/components/shadcn/pagination_item_component.rb +53 -0
- data/app/components/shadcn/pagination_next_component.rb +48 -0
- data/app/components/shadcn/pagination_previous_component.rb +48 -0
- data/app/components/shadcn/popover_component.rb +76 -0
- data/app/components/shadcn/popover_content_component.rb +25 -0
- data/app/components/shadcn/progress_component.rb +77 -0
- data/app/components/shadcn/radio_group_component.rb +129 -0
- data/app/components/shadcn/radio_group_item_component.rb +109 -0
- data/app/components/shadcn/resizable_handle_component.rb +98 -0
- data/app/components/shadcn/resizable_panel_component.rb +56 -0
- data/app/components/shadcn/resizable_panel_group_component.rb +94 -0
- data/app/components/shadcn/scroll_area_component.rb +110 -0
- data/app/components/shadcn/select_component.rb +151 -0
- data/app/components/shadcn/select_group_component.rb +32 -0
- data/app/components/shadcn/select_item_component.rb +59 -0
- data/app/components/shadcn/select_separator_component.rb +12 -0
- data/app/components/shadcn/separator_component.rb +54 -0
- data/app/components/shadcn/sheet_component.rb +82 -0
- data/app/components/shadcn/sheet_content_component.rb +95 -0
- data/app/components/shadcn/sheet_description_component.rb +12 -0
- data/app/components/shadcn/sheet_footer_component.rb +12 -0
- data/app/components/shadcn/sheet_header_component.rb +19 -0
- data/app/components/shadcn/sheet_title_component.rb +12 -0
- data/app/components/shadcn/sidebar_component.rb +180 -0
- data/app/components/shadcn/sidebar_content_component.rb +32 -0
- data/app/components/shadcn/sidebar_footer_component.rb +24 -0
- data/app/components/shadcn/sidebar_group_action_component.rb +26 -0
- data/app/components/shadcn/sidebar_group_component.rb +38 -0
- data/app/components/shadcn/sidebar_group_content_component.rb +32 -0
- data/app/components/shadcn/sidebar_group_label_component.rb +25 -0
- data/app/components/shadcn/sidebar_header_component.rb +24 -0
- data/app/components/shadcn/sidebar_inset_component.rb +25 -0
- data/app/components/shadcn/sidebar_menu_action_component.rb +37 -0
- data/app/components/shadcn/sidebar_menu_badge_component.rb +25 -0
- data/app/components/shadcn/sidebar_menu_button_component.rb +52 -0
- data/app/components/shadcn/sidebar_menu_component.rb +32 -0
- data/app/components/shadcn/sidebar_menu_item_component.rb +41 -0
- data/app/components/shadcn/sidebar_menu_skeleton_component.rb +46 -0
- data/app/components/shadcn/sidebar_menu_sub_button_component.rb +43 -0
- data/app/components/shadcn/sidebar_menu_sub_component.rb +33 -0
- data/app/components/shadcn/sidebar_menu_sub_item_component.rb +30 -0
- data/app/components/shadcn/sidebar_provider_component.rb +57 -0
- data/app/components/shadcn/sidebar_rail_component.rb +30 -0
- data/app/components/shadcn/sidebar_separator_component.rb +24 -0
- data/app/components/shadcn/sidebar_trigger_component.rb +51 -0
- data/app/components/shadcn/skeleton_component.rb +29 -0
- data/app/components/shadcn/slider_component.rb +76 -0
- data/app/components/shadcn/spinner_component.rb +67 -0
- data/app/components/shadcn/switch_component.rb +147 -0
- data/app/components/shadcn/table_body_component.rb +16 -0
- data/app/components/shadcn/table_caption_component.rb +12 -0
- data/app/components/shadcn/table_cell_component.rb +12 -0
- data/app/components/shadcn/table_component.rb +57 -0
- data/app/components/shadcn/table_footer_component.rb +16 -0
- data/app/components/shadcn/table_head_component.rb +12 -0
- data/app/components/shadcn/table_header_component.rb +16 -0
- data/app/components/shadcn/table_row_component.rb +40 -0
- data/app/components/shadcn/tabs_component.rb +78 -0
- data/app/components/shadcn/tabs_content_component.rb +32 -0
- data/app/components/shadcn/tabs_list_component.rb +30 -0
- data/app/components/shadcn/tabs_trigger_component.rb +37 -0
- data/app/components/shadcn/textarea_component.rb +84 -0
- data/app/components/shadcn/toast_action_component.rb +18 -0
- data/app/components/shadcn/toast_component.rb +114 -0
- data/app/components/shadcn/toast_description_component.rb +12 -0
- data/app/components/shadcn/toast_title_component.rb +12 -0
- data/app/components/shadcn/toast_viewport_component.rb +12 -0
- data/app/components/shadcn/toggle_component.rb +77 -0
- data/app/components/shadcn/toggle_group_component.rb +96 -0
- data/app/components/shadcn/toggle_group_item_component.rb +62 -0
- data/app/components/shadcn/tooltip_component.rb +89 -0
- data/app/components/shadcn/typography_component.rb +112 -0
- data/babel.config.cjs +5 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/config/importmap.rb +5 -0
- data/fly.toml +26 -0
- data/jest.config.js +19 -0
- data/jest.setup.js +8 -0
- data/lib/generators/shadcn/component/component_generator.rb +188 -0
- data/lib/generators/shadcn/install/install_generator.rb +140 -0
- data/lib/generators/shadcn/install/templates/initializer.rb.tt +35 -0
- data/lib/generators/shadcn/install/templates/shadcn.yml.tt +35 -0
- data/lib/generators/shadcn/theme/theme_generator.rb +128 -0
- data/lib/shadcn/rails/class_merger.rb +228 -0
- data/lib/shadcn/rails/configuration.rb +341 -0
- data/lib/shadcn/rails/engine.rb +59 -0
- data/lib/shadcn/rails/helpers/class_name_helper.rb +35 -0
- data/lib/shadcn/rails/helpers/component_helper.rb +60 -0
- data/lib/shadcn/rails/helpers/pagination_helper.rb +187 -0
- data/lib/shadcn/rails/version.rb +7 -0
- data/lib/shadcn/rails.rb +179 -0
- data/package-lock.json +7415 -0
- data/package.json +68 -0
- data/rollup.config.js +29 -0
- data/sig/shadcn/rails.rbs +6 -0
- metadata +526 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Combobox component - autocomplete input with searchable dropdown
|
|
5
|
+
# Composes Popover and Command components for a searchable select
|
|
6
|
+
#
|
|
7
|
+
# @example Basic combobox
|
|
8
|
+
# <%= render Shadcn::ComboboxComponent.new(
|
|
9
|
+
# items: [
|
|
10
|
+
# { value: "next", label: "Next.js" },
|
|
11
|
+
# { value: "remix", label: "Remix" },
|
|
12
|
+
# { value: "rails", label: "Ruby on Rails" }
|
|
13
|
+
# ],
|
|
14
|
+
# placeholder: "Select framework...",
|
|
15
|
+
# search_placeholder: "Search framework..."
|
|
16
|
+
# ) %>
|
|
17
|
+
#
|
|
18
|
+
# @example With selected value
|
|
19
|
+
# <%= render Shadcn::ComboboxComponent.new(
|
|
20
|
+
# items: frameworks,
|
|
21
|
+
# value: "rails",
|
|
22
|
+
# placeholder: "Select framework..."
|
|
23
|
+
# ) %>
|
|
24
|
+
#
|
|
25
|
+
# @example With name for form submission
|
|
26
|
+
# <%= render Shadcn::ComboboxComponent.new(
|
|
27
|
+
# items: frameworks,
|
|
28
|
+
# name: "project[framework]",
|
|
29
|
+
# placeholder: "Select framework..."
|
|
30
|
+
# ) %>
|
|
31
|
+
#
|
|
32
|
+
class ComboboxComponent < BaseComponent
|
|
33
|
+
TRIGGER_CLASSES = "w-[200px] justify-between"
|
|
34
|
+
POPOVER_CONTENT_CLASSES = "w-[200px] p-0"
|
|
35
|
+
CHEVRON_CLASSES = "ml-2 h-4 w-4 shrink-0 opacity-50"
|
|
36
|
+
CHECK_CLASSES = "mr-2 h-4 w-4"
|
|
37
|
+
|
|
38
|
+
# @param items [Array<Hash>] Array of items with :value and :label keys
|
|
39
|
+
# @param value [String, nil] Currently selected value
|
|
40
|
+
# @param placeholder [String] Placeholder text when no value selected
|
|
41
|
+
# @param search_placeholder [String] Placeholder text for search input
|
|
42
|
+
# @param empty_text [String] Text shown when no results found
|
|
43
|
+
# @param name [String, nil] Form field name for hidden input
|
|
44
|
+
# @param disabled [Boolean] Whether the combobox is disabled
|
|
45
|
+
# @param width [String] Width class for the component
|
|
46
|
+
def initialize(
|
|
47
|
+
items: [],
|
|
48
|
+
value: nil,
|
|
49
|
+
placeholder: "Select option...",
|
|
50
|
+
search_placeholder: "Search...",
|
|
51
|
+
empty_text: "No results found.",
|
|
52
|
+
name: nil,
|
|
53
|
+
disabled: false,
|
|
54
|
+
width: "w-[200px]",
|
|
55
|
+
**options
|
|
56
|
+
)
|
|
57
|
+
super(**options)
|
|
58
|
+
@items = items
|
|
59
|
+
@value = value
|
|
60
|
+
@placeholder = placeholder
|
|
61
|
+
@search_placeholder = search_placeholder
|
|
62
|
+
@empty_text = empty_text
|
|
63
|
+
@name = name
|
|
64
|
+
@disabled = disabled
|
|
65
|
+
@width = width
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def call
|
|
69
|
+
content_tag(:div, combobox_content, **combobox_attributes)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def combobox_content
|
|
75
|
+
safe_join([
|
|
76
|
+
hidden_input,
|
|
77
|
+
trigger_button,
|
|
78
|
+
popover_content_template
|
|
79
|
+
].compact)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def hidden_input
|
|
83
|
+
return unless @name
|
|
84
|
+
|
|
85
|
+
tag.input(
|
|
86
|
+
type: "hidden",
|
|
87
|
+
name: @name,
|
|
88
|
+
value: @value,
|
|
89
|
+
data: { "shadcn--combobox-target": "hiddenInput" }
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def trigger_button
|
|
94
|
+
content_tag(:button, button_content, **trigger_attributes)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def button_content
|
|
98
|
+
safe_join([
|
|
99
|
+
display_value,
|
|
100
|
+
chevron_icon
|
|
101
|
+
])
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def display_value
|
|
105
|
+
label = selected_label
|
|
106
|
+
content_tag(:span, label || @placeholder,
|
|
107
|
+
class: label ? nil : "text-muted-foreground",
|
|
108
|
+
data: { "shadcn--combobox-target": "displayValue" }
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def selected_label
|
|
113
|
+
return nil unless @value
|
|
114
|
+
|
|
115
|
+
item = @items.find { |i| i[:value].to_s == @value.to_s }
|
|
116
|
+
item&.dig(:label)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def chevron_icon
|
|
120
|
+
content_tag(:svg,
|
|
121
|
+
class: CHEVRON_CLASSES,
|
|
122
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
123
|
+
width: "24",
|
|
124
|
+
height: "24",
|
|
125
|
+
viewBox: "0 0 24 24",
|
|
126
|
+
fill: "none",
|
|
127
|
+
stroke: "currentColor",
|
|
128
|
+
"stroke-width": "2",
|
|
129
|
+
"stroke-linecap": "round",
|
|
130
|
+
"stroke-linejoin": "round"
|
|
131
|
+
) do
|
|
132
|
+
safe_join([
|
|
133
|
+
tag.path(d: "m7 15 5 5 5-5"),
|
|
134
|
+
tag.path(d: "m7 9 5-5 5 5")
|
|
135
|
+
])
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def trigger_attributes
|
|
140
|
+
{
|
|
141
|
+
type: "button",
|
|
142
|
+
role: "combobox",
|
|
143
|
+
class: cn(
|
|
144
|
+
"inline-flex items-center whitespace-nowrap rounded-md text-sm font-medium transition-colors",
|
|
145
|
+
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
146
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
147
|
+
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
|
148
|
+
"h-9 px-4 py-2",
|
|
149
|
+
@width,
|
|
150
|
+
"justify-between"
|
|
151
|
+
),
|
|
152
|
+
disabled: @disabled || nil,
|
|
153
|
+
"aria-expanded": "false",
|
|
154
|
+
data: {
|
|
155
|
+
"shadcn--combobox-target": "trigger",
|
|
156
|
+
action: "click->shadcn--combobox#toggle"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def popover_content_template
|
|
162
|
+
content_tag(:div, popover_inner_content, **popover_content_attributes)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def popover_inner_content
|
|
166
|
+
content_tag(:div, command_content, **command_wrapper_attributes)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def command_content
|
|
170
|
+
safe_join([
|
|
171
|
+
search_input,
|
|
172
|
+
items_list
|
|
173
|
+
])
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def search_input
|
|
177
|
+
content_tag(:div, class: "flex items-center border-b px-3") do
|
|
178
|
+
safe_join([
|
|
179
|
+
search_icon,
|
|
180
|
+
tag.input(
|
|
181
|
+
type: "text",
|
|
182
|
+
placeholder: @search_placeholder,
|
|
183
|
+
class: "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
184
|
+
data: {
|
|
185
|
+
"shadcn--combobox-target": "input",
|
|
186
|
+
action: "input->shadcn--combobox#filter"
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
])
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def search_icon
|
|
194
|
+
content_tag(:svg,
|
|
195
|
+
class: "mr-2 h-4 w-4 shrink-0 opacity-50",
|
|
196
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
197
|
+
width: "24",
|
|
198
|
+
height: "24",
|
|
199
|
+
viewBox: "0 0 24 24",
|
|
200
|
+
fill: "none",
|
|
201
|
+
stroke: "currentColor",
|
|
202
|
+
"stroke-width": "2",
|
|
203
|
+
"stroke-linecap": "round",
|
|
204
|
+
"stroke-linejoin": "round"
|
|
205
|
+
) do
|
|
206
|
+
safe_join([
|
|
207
|
+
tag.circle(cx: "11", cy: "11", r: "8"),
|
|
208
|
+
tag.path(d: "m21 21-4.3-4.3")
|
|
209
|
+
])
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def items_list
|
|
214
|
+
content_tag(:div, items_content, class: "max-h-[300px] overflow-y-auto overflow-x-hidden", data: { "shadcn--combobox-target": "list" })
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def items_content
|
|
218
|
+
safe_join([
|
|
219
|
+
empty_state,
|
|
220
|
+
items_group
|
|
221
|
+
])
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def empty_state
|
|
225
|
+
# Always start hidden - will show only when user types and no results match
|
|
226
|
+
content_tag(:div, @empty_text,
|
|
227
|
+
class: "py-6 text-center text-sm text-muted-foreground",
|
|
228
|
+
hidden: true,
|
|
229
|
+
data: { "shadcn--combobox-target": "empty" }
|
|
230
|
+
)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def items_group
|
|
234
|
+
content_tag(:div, class: "overflow-hidden p-1 text-foreground") do
|
|
235
|
+
safe_join(@items.map { |item| render_item(item) })
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def render_item(item)
|
|
240
|
+
is_selected = @value.to_s == item[:value].to_s
|
|
241
|
+
|
|
242
|
+
content_tag(:div, **item_attributes(item, is_selected)) do
|
|
243
|
+
safe_join([
|
|
244
|
+
check_icon(is_selected),
|
|
245
|
+
item[:label]
|
|
246
|
+
])
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def check_icon(is_selected)
|
|
251
|
+
content_tag(:svg,
|
|
252
|
+
class: cn(CHECK_CLASSES, is_selected ? "opacity-100" : "opacity-0"),
|
|
253
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
254
|
+
width: "24",
|
|
255
|
+
height: "24",
|
|
256
|
+
viewBox: "0 0 24 24",
|
|
257
|
+
fill: "none",
|
|
258
|
+
stroke: "currentColor",
|
|
259
|
+
"stroke-width": "2",
|
|
260
|
+
"stroke-linecap": "round",
|
|
261
|
+
"stroke-linejoin": "round"
|
|
262
|
+
) do
|
|
263
|
+
tag.path(d: "M20 6 9 17l-5-5")
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def item_attributes(item, is_selected)
|
|
268
|
+
{
|
|
269
|
+
class: cn(
|
|
270
|
+
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none",
|
|
271
|
+
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground",
|
|
272
|
+
"data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50",
|
|
273
|
+
"hover:bg-accent hover:text-accent-foreground cursor-pointer"
|
|
274
|
+
),
|
|
275
|
+
role: "option",
|
|
276
|
+
tabindex: "0",
|
|
277
|
+
data: {
|
|
278
|
+
"shadcn--combobox-target": "item",
|
|
279
|
+
value: item[:value],
|
|
280
|
+
label: item[:label],
|
|
281
|
+
selected: is_selected,
|
|
282
|
+
action: "click->shadcn--combobox#select"
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def popover_content_attributes
|
|
288
|
+
{
|
|
289
|
+
class: cn(
|
|
290
|
+
"absolute z-50 mt-1 rounded-md border bg-popover text-popover-foreground shadow-md outline-none",
|
|
291
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
292
|
+
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
293
|
+
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
294
|
+
@width,
|
|
295
|
+
"p-0"
|
|
296
|
+
),
|
|
297
|
+
hidden: true,
|
|
298
|
+
data: {
|
|
299
|
+
"shadcn--combobox-target": "content",
|
|
300
|
+
state: "closed"
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def command_wrapper_attributes
|
|
306
|
+
{
|
|
307
|
+
class: "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground"
|
|
308
|
+
}
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def combobox_attributes
|
|
312
|
+
{
|
|
313
|
+
class: cn("relative inline-block", class_name),
|
|
314
|
+
data: {
|
|
315
|
+
controller: "shadcn--combobox",
|
|
316
|
+
"shadcn--combobox-value-value": @value,
|
|
317
|
+
action: "keydown.escape->shadcn--combobox#close click@window->shadcn--combobox#handleClickOutside"
|
|
318
|
+
}
|
|
319
|
+
}.merge(html_options).merge(build_data)
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Command component - a command palette for searching and selecting items
|
|
5
|
+
# Matches shadcn/ui Command component (cmdk pattern)
|
|
6
|
+
#
|
|
7
|
+
# @example Basic command
|
|
8
|
+
# <%= render Shadcn::CommandComponent.new do |command| %>
|
|
9
|
+
# <% command.with_input(placeholder: "Type a command...") %>
|
|
10
|
+
# <% command.with_list do |list| %>
|
|
11
|
+
# <% list.with_empty { "No results found." } %>
|
|
12
|
+
# <% list.with_group(heading: "Suggestions") do |group| %>
|
|
13
|
+
# <% group.with_item(value: "calendar") { "Calendar" } %>
|
|
14
|
+
# <% group.with_item(value: "search") { "Search" } %>
|
|
15
|
+
# <% end %>
|
|
16
|
+
# <% end %>
|
|
17
|
+
# <% end %>
|
|
18
|
+
#
|
|
19
|
+
class CommandComponent < BaseComponent
|
|
20
|
+
BASE_CLASSES = "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground"
|
|
21
|
+
|
|
22
|
+
# Input slot for search
|
|
23
|
+
renders_one :input, lambda { |placeholder: "Type a command or search...", **options|
|
|
24
|
+
CommandInputComponent.new(placeholder: placeholder, **options)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# List slot containing groups and items
|
|
28
|
+
renders_one :list, lambda { |**options|
|
|
29
|
+
CommandListComponent.new(**options)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
def call
|
|
33
|
+
content_tag(:div, command_content, **command_attributes)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def command_content
|
|
39
|
+
safe_join([input, list, content].compact)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def command_attributes
|
|
43
|
+
{
|
|
44
|
+
class: merge_classes(BASE_CLASSES),
|
|
45
|
+
data: {
|
|
46
|
+
controller: "shadcn--command",
|
|
47
|
+
action: "keydown->shadcn--command#handleKeydown"
|
|
48
|
+
}
|
|
49
|
+
}.merge(html_options).merge(build_data)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Command Dialog component - command palette in a modal dialog
|
|
5
|
+
# Combines Dialog and Command components
|
|
6
|
+
#
|
|
7
|
+
# @example Basic command dialog
|
|
8
|
+
# <%= render Shadcn::CommandDialogComponent.new do |dialog| %>
|
|
9
|
+
# <% dialog.with_trigger do %>
|
|
10
|
+
# <button>Open Command</button>
|
|
11
|
+
# <% end %>
|
|
12
|
+
# <% dialog.with_command do |command| %>
|
|
13
|
+
# <% command.with_input(placeholder: "Type a command...") %>
|
|
14
|
+
# <% command.with_list do |list| %>
|
|
15
|
+
# <% list.with_empty { "No results found." } %>
|
|
16
|
+
# <% list.with_group(heading: "Suggestions") do |group| %>
|
|
17
|
+
# <% group.with_item(value: "calendar") { "Calendar" } %>
|
|
18
|
+
# <% end %>
|
|
19
|
+
# <% end %>
|
|
20
|
+
# <% end %>
|
|
21
|
+
# <% end %>
|
|
22
|
+
#
|
|
23
|
+
class CommandDialogComponent < BaseComponent
|
|
24
|
+
OVERLAY_CLASSES = "fixed inset-0 z-50 bg-black/80"
|
|
25
|
+
CONTENT_CLASSES = "fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%] shadow-lg"
|
|
26
|
+
COMMAND_CLASSES = "[&_[data-shadcn--command-target='input']]:h-12"
|
|
27
|
+
|
|
28
|
+
# Trigger slot - simple wrapper that renders content
|
|
29
|
+
renders_one :trigger, lambda { |**options, &block|
|
|
30
|
+
content_tag(:div, data: { "shadcn--command-dialog-target": "trigger", action: "click->shadcn--command-dialog#open" }, **options, &block)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Command slot
|
|
34
|
+
renders_one :command, lambda { |**options|
|
|
35
|
+
CommandComponent.new(class_name: "rounded-lg border shadow-md md:min-w-[450px] #{COMMAND_CLASSES}", **options)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# @param shortcut [String] Keyboard shortcut to open (e.g., "k" for Cmd+K)
|
|
39
|
+
def initialize(shortcut: nil, **options)
|
|
40
|
+
super(**options)
|
|
41
|
+
@shortcut = shortcut
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def call
|
|
45
|
+
content_tag(:div, dialog_content, **dialog_attributes)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def dialog_content
|
|
51
|
+
safe_join([
|
|
52
|
+
trigger,
|
|
53
|
+
dialog_template
|
|
54
|
+
].compact)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def dialog_template
|
|
58
|
+
content_tag(:template, data: { "shadcn--command-dialog-target": "template" }) do
|
|
59
|
+
safe_join([
|
|
60
|
+
content_tag(:div, "", class: OVERLAY_CLASSES, data: { "shadcn--command-dialog-target": "overlay", action: "click->shadcn--command-dialog#close" }),
|
|
61
|
+
content_tag(:div, command || content, class: CONTENT_CLASSES, data: { "shadcn--command-dialog-target": "content" })
|
|
62
|
+
])
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def dialog_attributes
|
|
67
|
+
attrs = {
|
|
68
|
+
data: {
|
|
69
|
+
controller: "shadcn--command-dialog",
|
|
70
|
+
"shadcn--command-dialog-shortcut-value": @shortcut
|
|
71
|
+
}.compact
|
|
72
|
+
}
|
|
73
|
+
attrs.merge(html_options).merge(build_data)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Command Empty component - shown when no results match
|
|
5
|
+
class CommandEmptyComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "py-6 text-center text-sm text-muted-foreground"
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
content_tag(:div, content.presence || "No results found.", class: merge_classes(BASE_CLASSES), data: { "shadcn--command-target": "empty" }, **html_options.merge(build_data))
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Command Group component - groups related command items
|
|
5
|
+
class CommandGroupComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "overflow-hidden p-1 text-foreground"
|
|
7
|
+
HEADING_CLASSES = "px-2 py-1.5 text-xs font-medium text-muted-foreground"
|
|
8
|
+
|
|
9
|
+
# Items in this group
|
|
10
|
+
renders_many :items, lambda { |value: nil, disabled: false, **options|
|
|
11
|
+
CommandItemComponent.new(value: value, disabled: disabled, **options)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# @param heading [String, nil] Optional heading for the group
|
|
15
|
+
def initialize(heading: nil, **options)
|
|
16
|
+
super(**options)
|
|
17
|
+
@heading = heading
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call
|
|
21
|
+
content_tag(:div, group_content, class: merge_classes(BASE_CLASSES), role: "group", data: { "shadcn--command-target": "group" }, **html_options.merge(build_data))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def group_content
|
|
27
|
+
parts = []
|
|
28
|
+
parts << content_tag(:div, @heading, class: HEADING_CLASSES, "aria-hidden": true) if @heading.present?
|
|
29
|
+
parts << safe_join(items)
|
|
30
|
+
parts << content
|
|
31
|
+
safe_join(parts.compact)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Command Input component - search input for command palette
|
|
5
|
+
class CommandInputComponent < BaseComponent
|
|
6
|
+
WRAPPER_CLASSES = "flex items-center border-b px-3"
|
|
7
|
+
INPUT_CLASSES = "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
|
|
8
|
+
ICON_CLASSES = "mr-2 h-4 w-4 shrink-0 opacity-50"
|
|
9
|
+
|
|
10
|
+
# @param placeholder [String] Placeholder text
|
|
11
|
+
# @param autofocus [Boolean] Whether to autofocus the input
|
|
12
|
+
def initialize(placeholder: "Type a command or search...", autofocus: false, **options)
|
|
13
|
+
super(**options)
|
|
14
|
+
@placeholder = placeholder
|
|
15
|
+
@autofocus = autofocus
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
content_tag(:div, class: WRAPPER_CLASSES) do
|
|
20
|
+
safe_join([
|
|
21
|
+
search_icon,
|
|
22
|
+
tag.input(
|
|
23
|
+
type: "text",
|
|
24
|
+
placeholder: @placeholder,
|
|
25
|
+
autofocus: @autofocus || nil,
|
|
26
|
+
class: merge_classes(INPUT_CLASSES),
|
|
27
|
+
data: {
|
|
28
|
+
"shadcn--command-target": "input",
|
|
29
|
+
action: "input->shadcn--command#filter"
|
|
30
|
+
},
|
|
31
|
+
**html_options.except(:class)
|
|
32
|
+
)
|
|
33
|
+
])
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def search_icon
|
|
40
|
+
content_tag(:svg,
|
|
41
|
+
class: ICON_CLASSES,
|
|
42
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
43
|
+
width: "24",
|
|
44
|
+
height: "24",
|
|
45
|
+
viewBox: "0 0 24 24",
|
|
46
|
+
fill: "none",
|
|
47
|
+
stroke: "currentColor",
|
|
48
|
+
"stroke-width": "2",
|
|
49
|
+
"stroke-linecap": "round",
|
|
50
|
+
"stroke-linejoin": "round"
|
|
51
|
+
) do
|
|
52
|
+
safe_join([
|
|
53
|
+
tag.circle(cx: "11", cy: "11", r: "8"),
|
|
54
|
+
tag.path(d: "m21 21-4.3-4.3")
|
|
55
|
+
])
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Command Item component - individual selectable command
|
|
5
|
+
class CommandItemComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0"
|
|
7
|
+
|
|
8
|
+
# Shortcut slot
|
|
9
|
+
renders_one :shortcut, lambda { |**options|
|
|
10
|
+
CommandShortcutComponent.new(**options)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
# @param value [String, nil] The searchable value (defaults to content text)
|
|
14
|
+
# @param disabled [Boolean] Whether the item is disabled
|
|
15
|
+
# @param on_select [String] JavaScript to execute on select
|
|
16
|
+
def initialize(value: nil, disabled: false, on_select: nil, **options)
|
|
17
|
+
super(**options)
|
|
18
|
+
@value = value
|
|
19
|
+
@disabled = disabled
|
|
20
|
+
@on_select = on_select
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call
|
|
24
|
+
content_tag(:div, item_content, **item_attributes)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def item_content
|
|
30
|
+
safe_join([content, shortcut].compact)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def item_attributes
|
|
34
|
+
{
|
|
35
|
+
class: merge_classes(BASE_CLASSES),
|
|
36
|
+
role: "option",
|
|
37
|
+
tabindex: @disabled ? nil : "0",
|
|
38
|
+
data: {
|
|
39
|
+
"shadcn--command-target": "item",
|
|
40
|
+
value: @value,
|
|
41
|
+
disabled: @disabled || nil,
|
|
42
|
+
selected: false,
|
|
43
|
+
action: @disabled ? nil : "click->shadcn--command#select"
|
|
44
|
+
}.compact
|
|
45
|
+
}.merge(html_options).merge(build_data)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Command List component - container for command groups and items
|
|
5
|
+
class CommandListComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "max-h-[300px] overflow-y-auto overflow-x-hidden"
|
|
7
|
+
|
|
8
|
+
# Empty state slot
|
|
9
|
+
renders_one :empty, lambda { |**options|
|
|
10
|
+
CommandEmptyComponent.new(**options)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
# Groups of items
|
|
14
|
+
renders_many :groups, lambda { |heading: nil, **options|
|
|
15
|
+
CommandGroupComponent.new(heading: heading, **options)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# Direct items (without group)
|
|
19
|
+
renders_many :items, lambda { |value: nil, disabled: false, **options|
|
|
20
|
+
CommandItemComponent.new(value: value, disabled: disabled, **options)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Separators
|
|
24
|
+
renders_many :separators, lambda { |**options|
|
|
25
|
+
CommandSeparatorComponent.new(**options)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
def call
|
|
29
|
+
content_tag(:div, list_content, class: merge_classes(BASE_CLASSES), data: { "shadcn--command-target": "list" }, **html_options.merge(build_data))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def list_content
|
|
35
|
+
safe_join([empty, groups, items, separators, content].flatten.compact)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Command Separator component - visual divider between groups
|
|
5
|
+
class CommandSeparatorComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "-mx-1 h-px bg-border"
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
content_tag(:div, "", class: merge_classes(BASE_CLASSES), role: "separator", **html_options.merge(build_data))
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Command Shortcut component - displays keyboard shortcut hint
|
|
5
|
+
class CommandShortcutComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "ml-auto text-xs tracking-widest text-muted-foreground"
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
content_tag(:span, content, class: merge_classes(BASE_CLASSES), **html_options.merge(build_data))
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|