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,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Context Menu component
|
|
5
|
+
# Matches shadcn/ui ContextMenu component
|
|
6
|
+
# Displays a menu when the user right-clicks on an element
|
|
7
|
+
#
|
|
8
|
+
# @example Basic context menu
|
|
9
|
+
# <%= render Shadcn::ContextMenuComponent.new do |menu| %>
|
|
10
|
+
# <% menu.with_trigger do %>
|
|
11
|
+
# <div class="border border-dashed p-8 text-center">
|
|
12
|
+
# Right click here
|
|
13
|
+
# </div>
|
|
14
|
+
# <% end %>
|
|
15
|
+
# <% menu.with_menu do |content| %>
|
|
16
|
+
# <% content.with_item(href: "#") { "Back" } %>
|
|
17
|
+
# <% content.with_item(href: "#", disabled: true) { "Forward" } %>
|
|
18
|
+
# <% content.with_item(href: "#") { "Reload" } %>
|
|
19
|
+
# <% content.with_separator %>
|
|
20
|
+
# <% content.with_item(href: "#") { "Save As..." } %>
|
|
21
|
+
# <% content.with_item(href: "#") { "Print" } %>
|
|
22
|
+
# <% end %>
|
|
23
|
+
# <% end %>
|
|
24
|
+
#
|
|
25
|
+
class ContextMenuComponent < BaseComponent
|
|
26
|
+
renders_one :trigger
|
|
27
|
+
renders_one :menu, lambda { |**options|
|
|
28
|
+
ContextMenuContentComponent.new(**options)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def call
|
|
32
|
+
content_tag(:div, context_menu_content, context_menu_attributes)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def context_menu_content
|
|
38
|
+
safe_join([
|
|
39
|
+
trigger_wrapper,
|
|
40
|
+
menu
|
|
41
|
+
].compact)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def trigger_wrapper
|
|
45
|
+
return unless trigger
|
|
46
|
+
|
|
47
|
+
content_tag(:div, trigger, {
|
|
48
|
+
"data-shadcn--context-menu-target": "trigger",
|
|
49
|
+
"data-action": "contextmenu->shadcn--context-menu#show:prevent"
|
|
50
|
+
})
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def context_menu_attributes
|
|
54
|
+
attrs = {
|
|
55
|
+
class: cn("relative inline-block", class_name),
|
|
56
|
+
"data-controller": "shadcn--context-menu",
|
|
57
|
+
"data-action": "keydown.escape->shadcn--context-menu#close"
|
|
58
|
+
}
|
|
59
|
+
attrs.merge!(html_options)
|
|
60
|
+
attrs.merge!(build_data)
|
|
61
|
+
attrs.compact
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Context Menu Content component
|
|
5
|
+
class ContextMenuContentComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
|
|
7
|
+
|
|
8
|
+
renders_many :items, lambda { |**options, &block|
|
|
9
|
+
ContextMenuItemComponent.new(**options, &block)
|
|
10
|
+
}
|
|
11
|
+
renders_many :labels, lambda { |**options, &block|
|
|
12
|
+
ContextMenuLabelComponent.new(**options, &block)
|
|
13
|
+
}
|
|
14
|
+
renders_many :separators, lambda { |**options|
|
|
15
|
+
ContextMenuSeparatorComponent.new(**options)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
content_tag(:div, menu_content, menu_attributes)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def menu_content
|
|
25
|
+
if items.any? || labels.any? || separators.any?
|
|
26
|
+
safe_join([labels, items, separators, content].flatten.compact)
|
|
27
|
+
else
|
|
28
|
+
content
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def menu_attributes
|
|
33
|
+
{
|
|
34
|
+
class: merge_classes(BASE_CLASSES),
|
|
35
|
+
role: "menu",
|
|
36
|
+
"aria-orientation": "vertical",
|
|
37
|
+
"data-shadcn--context-menu-target": "content",
|
|
38
|
+
"data-state": "closed",
|
|
39
|
+
style: "position: fixed;",
|
|
40
|
+
hidden: true
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Context Menu Item component
|
|
5
|
+
class ContextMenuItemComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0"
|
|
7
|
+
|
|
8
|
+
VARIANTS = {
|
|
9
|
+
default: "",
|
|
10
|
+
destructive: "text-destructive focus:bg-destructive focus:text-destructive-foreground"
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
renders_one :shortcut, lambda { |**options|
|
|
14
|
+
ContextMenuShortcutComponent.new(**options)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# @param href [String, nil] Link URL
|
|
18
|
+
# @param variant [Symbol] Item variant (:default, :destructive)
|
|
19
|
+
# @param disabled [Boolean] Whether item is disabled
|
|
20
|
+
# @param inset [Boolean] Whether to add left padding for icons
|
|
21
|
+
def initialize(href: nil, variant: :default, disabled: false, inset: false, **options, &block)
|
|
22
|
+
super(**options, &block)
|
|
23
|
+
@href = href
|
|
24
|
+
@variant = variant.to_sym
|
|
25
|
+
@disabled = disabled
|
|
26
|
+
@inset = inset
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def call
|
|
30
|
+
tag_name = @href ? :a : :div
|
|
31
|
+
content_tag(tag_name, item_content, item_attributes)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def item_content
|
|
37
|
+
safe_join([content, shortcut].compact)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def item_classes
|
|
41
|
+
cn(
|
|
42
|
+
BASE_CLASSES,
|
|
43
|
+
VARIANTS[@variant],
|
|
44
|
+
@inset ? "pl-8" : "",
|
|
45
|
+
class_name
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def item_attributes
|
|
50
|
+
attrs = {
|
|
51
|
+
class: item_classes,
|
|
52
|
+
role: "menuitem",
|
|
53
|
+
tabindex: @disabled ? nil : "-1",
|
|
54
|
+
href: @href,
|
|
55
|
+
"data-disabled": @disabled ? "" : nil,
|
|
56
|
+
"data-action": "click->shadcn--context-menu#selectItem"
|
|
57
|
+
}
|
|
58
|
+
attrs.merge!(html_options)
|
|
59
|
+
attrs.merge!(build_data)
|
|
60
|
+
attrs.compact
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Context Menu Label component
|
|
5
|
+
class ContextMenuLabelComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "px-2 py-1.5 text-sm font-semibold"
|
|
7
|
+
|
|
8
|
+
# @param inset [Boolean] Whether to add left padding
|
|
9
|
+
def initialize(inset: false, **options, &block)
|
|
10
|
+
super(**options, &block)
|
|
11
|
+
@inset = inset
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
content_tag(:div, content, class: cn(BASE_CLASSES, @inset ? "pl-8" : "", class_name))
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Context Menu Separator component
|
|
5
|
+
class ContextMenuSeparatorComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "-mx-1 my-1 h-px bg-muted"
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
content_tag(:div, "", class: merge_classes(BASE_CLASSES), role: "separator")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Context Menu Shortcut component
|
|
5
|
+
class ContextMenuShortcutComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "ml-auto text-xs tracking-widest opacity-60"
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
content_tag(:span, content, class: merge_classes(BASE_CLASSES))
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Date Picker component - a button that opens a calendar popover
|
|
5
|
+
# Composition of Popover and Calendar components
|
|
6
|
+
# Matches shadcn/ui Date Picker component
|
|
7
|
+
#
|
|
8
|
+
# @example Basic date picker
|
|
9
|
+
# <%= render Shadcn::DatePickerComponent.new %>
|
|
10
|
+
#
|
|
11
|
+
# @example With selected date
|
|
12
|
+
# <%= render Shadcn::DatePickerComponent.new(selected: Date.today) %>
|
|
13
|
+
#
|
|
14
|
+
# @example With name for form submission
|
|
15
|
+
# <%= render Shadcn::DatePickerComponent.new(name: "event[date]") %>
|
|
16
|
+
#
|
|
17
|
+
# @example With date constraints
|
|
18
|
+
# <%= render Shadcn::DatePickerComponent.new(
|
|
19
|
+
# min_date: Date.today,
|
|
20
|
+
# max_date: Date.today + 30.days
|
|
21
|
+
# ) %>
|
|
22
|
+
#
|
|
23
|
+
class DatePickerComponent < BaseComponent
|
|
24
|
+
TRIGGER_CLASSES = "inline-flex items-center justify-start whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2 w-[240px] pl-3 text-left"
|
|
25
|
+
PLACEHOLDER_CLASSES = "text-muted-foreground"
|
|
26
|
+
CONTENT_CLASSES = "p-0 w-auto"
|
|
27
|
+
|
|
28
|
+
MONTHS = %w[January February March April May June July August September October November December].freeze
|
|
29
|
+
|
|
30
|
+
# @param selected [Date, nil] Currently selected date
|
|
31
|
+
# @param month [Date, nil] Month to display (defaults to current month)
|
|
32
|
+
# @param min_date [Date, nil] Minimum selectable date
|
|
33
|
+
# @param max_date [Date, nil] Maximum selectable date
|
|
34
|
+
# @param name [String, nil] Form field name for hidden input
|
|
35
|
+
# @param disabled_dates [Array<Date>] Specific dates that cannot be selected
|
|
36
|
+
# @param disabled_days_of_week [Array<Integer>] Days of week to disable (0=Sun, 6=Sat)
|
|
37
|
+
# @param show_outside_days [Boolean] Whether to show days outside current month
|
|
38
|
+
# @param week_starts_on [Integer] First day of week (0=Sunday, 1=Monday, etc.)
|
|
39
|
+
# @param placeholder [String] Placeholder text when no date selected
|
|
40
|
+
# @param format [Symbol] Date format (:short, :medium, :long)
|
|
41
|
+
# @param disabled [Boolean] Whether the date picker is disabled
|
|
42
|
+
def initialize(
|
|
43
|
+
selected: nil,
|
|
44
|
+
month: nil,
|
|
45
|
+
min_date: nil,
|
|
46
|
+
max_date: nil,
|
|
47
|
+
name: nil,
|
|
48
|
+
disabled_dates: [],
|
|
49
|
+
disabled_days_of_week: [],
|
|
50
|
+
show_outside_days: true,
|
|
51
|
+
week_starts_on: 0,
|
|
52
|
+
placeholder: "Pick a date",
|
|
53
|
+
format: :medium,
|
|
54
|
+
disabled: false,
|
|
55
|
+
**options
|
|
56
|
+
)
|
|
57
|
+
super(**options)
|
|
58
|
+
@selected = selected
|
|
59
|
+
@month = month || selected || Date.today
|
|
60
|
+
@min_date = min_date
|
|
61
|
+
@max_date = max_date
|
|
62
|
+
@name = name
|
|
63
|
+
@disabled_dates = disabled_dates
|
|
64
|
+
@disabled_days_of_week = disabled_days_of_week
|
|
65
|
+
@show_outside_days = show_outside_days
|
|
66
|
+
@week_starts_on = week_starts_on
|
|
67
|
+
@placeholder = placeholder
|
|
68
|
+
@format = format
|
|
69
|
+
@disabled = disabled
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def call
|
|
73
|
+
content_tag(:div, picker_content, picker_attributes)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def picker_content
|
|
79
|
+
safe_join([
|
|
80
|
+
hidden_input,
|
|
81
|
+
trigger_button,
|
|
82
|
+
popover_content
|
|
83
|
+
].compact)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def hidden_input
|
|
87
|
+
return unless @name
|
|
88
|
+
|
|
89
|
+
tag.input(
|
|
90
|
+
type: "hidden",
|
|
91
|
+
name: @name,
|
|
92
|
+
value: @selected&.iso8601,
|
|
93
|
+
data: { "shadcn--date-picker-target": "hiddenInput" }
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def trigger_button
|
|
98
|
+
content_tag(:button,
|
|
99
|
+
trigger_content,
|
|
100
|
+
type: "button",
|
|
101
|
+
class: cn(TRIGGER_CLASSES, ("cursor-not-allowed opacity-50" if @disabled)),
|
|
102
|
+
disabled: @disabled || nil,
|
|
103
|
+
"aria-haspopup": "dialog",
|
|
104
|
+
"aria-expanded": "false",
|
|
105
|
+
data: {
|
|
106
|
+
"shadcn--date-picker-target": "trigger",
|
|
107
|
+
action: "click->shadcn--date-picker#toggle"
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def trigger_content
|
|
113
|
+
safe_join([
|
|
114
|
+
calendar_icon,
|
|
115
|
+
date_display
|
|
116
|
+
])
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def calendar_icon
|
|
120
|
+
content_tag(:svg,
|
|
121
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
122
|
+
width: "16",
|
|
123
|
+
height: "16",
|
|
124
|
+
viewBox: "0 0 24 24",
|
|
125
|
+
fill: "none",
|
|
126
|
+
stroke: "currentColor",
|
|
127
|
+
"stroke-width": "2",
|
|
128
|
+
"stroke-linecap": "round",
|
|
129
|
+
"stroke-linejoin": "round",
|
|
130
|
+
class: "mr-2 h-4 w-4"
|
|
131
|
+
) do
|
|
132
|
+
safe_join([
|
|
133
|
+
tag.path(d: "M8 2v4"),
|
|
134
|
+
tag.path(d: "M16 2v4"),
|
|
135
|
+
tag.rect(width: "18", height: "18", x: "3", y: "4", rx: "2"),
|
|
136
|
+
tag.path(d: "M3 10h18")
|
|
137
|
+
])
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def date_display
|
|
142
|
+
if @selected
|
|
143
|
+
content_tag(:span, format_date(@selected), data: { "shadcn--date-picker-target": "displayValue" })
|
|
144
|
+
else
|
|
145
|
+
content_tag(:span, @placeholder, class: PLACEHOLDER_CLASSES, data: { "shadcn--date-picker-target": "displayValue" })
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def format_date(date)
|
|
150
|
+
case @format
|
|
151
|
+
when :short
|
|
152
|
+
date.strftime("%m/%d/%Y")
|
|
153
|
+
when :medium
|
|
154
|
+
date.strftime("%B %-d, %Y") # e.g., "November 26, 2024"
|
|
155
|
+
when :long
|
|
156
|
+
date.strftime("%A, %B %-d, %Y") # e.g., "Tuesday, November 26, 2024"
|
|
157
|
+
when :iso
|
|
158
|
+
date.iso8601
|
|
159
|
+
else
|
|
160
|
+
date.strftime("%B %-d, %Y")
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def popover_content
|
|
165
|
+
content_tag(:div,
|
|
166
|
+
calendar,
|
|
167
|
+
class: cn("absolute z-50 mt-1 rounded-md border bg-popover shadow-md", CONTENT_CLASSES),
|
|
168
|
+
"data-shadcn--date-picker-target": "content",
|
|
169
|
+
style: "display: none;"
|
|
170
|
+
)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def calendar
|
|
174
|
+
content_tag(:div, calendar_content, calendar_attributes)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def calendar_content
|
|
178
|
+
safe_join([
|
|
179
|
+
calendar_header,
|
|
180
|
+
weekday_header,
|
|
181
|
+
days_grid
|
|
182
|
+
])
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def calendar_header
|
|
186
|
+
content_tag(:div, class: "flex items-center justify-between p-3 pb-0") do
|
|
187
|
+
safe_join([
|
|
188
|
+
prev_month_button,
|
|
189
|
+
month_year_label,
|
|
190
|
+
next_month_button
|
|
191
|
+
])
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def prev_month_button
|
|
196
|
+
content_tag(:button,
|
|
197
|
+
chevron_left_icon,
|
|
198
|
+
type: "button",
|
|
199
|
+
class: "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground h-7 w-7",
|
|
200
|
+
"aria-label": "Previous month",
|
|
201
|
+
data: { action: "click->shadcn--date-picker#previousMonth" }
|
|
202
|
+
)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def next_month_button
|
|
206
|
+
content_tag(:button,
|
|
207
|
+
chevron_right_icon,
|
|
208
|
+
type: "button",
|
|
209
|
+
class: "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground h-7 w-7",
|
|
210
|
+
"aria-label": "Next month",
|
|
211
|
+
data: { action: "click->shadcn--date-picker#nextMonth" }
|
|
212
|
+
)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def month_year_label
|
|
216
|
+
content_tag(:div,
|
|
217
|
+
"#{MONTHS[@month.month - 1]} #{@month.year}",
|
|
218
|
+
class: "text-sm font-medium",
|
|
219
|
+
data: { "shadcn--date-picker-target": "monthYear" }
|
|
220
|
+
)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def weekday_header
|
|
224
|
+
content_tag(:div, class: "grid grid-cols-7 gap-1 p-3 pb-0") do
|
|
225
|
+
safe_join(CalendarComponent::WEEKDAYS.map do |day|
|
|
226
|
+
content_tag(:div, day, class: "text-center text-xs font-medium text-muted-foreground")
|
|
227
|
+
end)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def days_grid
|
|
232
|
+
content_tag(:div, class: "grid grid-cols-7 gap-1 p-3", data: { "shadcn--date-picker-target": "grid" }) do
|
|
233
|
+
safe_join(calendar_days.map { |day| render_day(day) })
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def calendar_days
|
|
238
|
+
first_day = @month.beginning_of_month
|
|
239
|
+
last_day = @month.end_of_month
|
|
240
|
+
|
|
241
|
+
start_date = first_day.beginning_of_week(:sunday)
|
|
242
|
+
end_date = last_day.end_of_week(:sunday)
|
|
243
|
+
|
|
244
|
+
(start_date..end_date).to_a
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def render_day(date)
|
|
248
|
+
is_outside = date.month != @month.month
|
|
249
|
+
is_selected = @selected && date == @selected
|
|
250
|
+
is_today = date == Date.today
|
|
251
|
+
is_disabled = date_disabled?(date)
|
|
252
|
+
|
|
253
|
+
return empty_day if is_outside && !@show_outside_days
|
|
254
|
+
|
|
255
|
+
classes = ["h-8 w-8 text-center text-sm p-0 relative flex items-center justify-center rounded-md cursor-pointer hover:bg-accent hover:text-accent-foreground focus:outline-none focus:ring-1 focus:ring-ring"]
|
|
256
|
+
classes << "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground" if is_selected
|
|
257
|
+
classes << "bg-accent text-accent-foreground" if is_today && !is_selected
|
|
258
|
+
classes << "text-muted-foreground opacity-50" if is_outside
|
|
259
|
+
classes << "text-muted-foreground opacity-50 pointer-events-none" if is_disabled
|
|
260
|
+
|
|
261
|
+
content_tag(:button,
|
|
262
|
+
date.day.to_s,
|
|
263
|
+
type: "button",
|
|
264
|
+
class: cn(*classes),
|
|
265
|
+
tabindex: is_disabled ? "-1" : "0",
|
|
266
|
+
"aria-selected": is_selected || nil,
|
|
267
|
+
"aria-disabled": is_disabled || nil,
|
|
268
|
+
disabled: is_disabled || nil,
|
|
269
|
+
data: {
|
|
270
|
+
date: date.iso8601,
|
|
271
|
+
"shadcn--date-picker-target": "day",
|
|
272
|
+
action: is_disabled ? nil : "click->shadcn--date-picker#selectDay"
|
|
273
|
+
}.compact
|
|
274
|
+
)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def empty_day
|
|
278
|
+
content_tag(:div, "", class: "h-8 w-8")
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def date_disabled?(date)
|
|
282
|
+
return true if @min_date && date < @min_date
|
|
283
|
+
return true if @max_date && date > @max_date
|
|
284
|
+
return true if @disabled_dates.include?(date)
|
|
285
|
+
|
|
286
|
+
false
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def chevron_left_icon
|
|
290
|
+
content_tag(:svg,
|
|
291
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
292
|
+
width: "16",
|
|
293
|
+
height: "16",
|
|
294
|
+
viewBox: "0 0 24 24",
|
|
295
|
+
fill: "none",
|
|
296
|
+
stroke: "currentColor",
|
|
297
|
+
"stroke-width": "2",
|
|
298
|
+
"stroke-linecap": "round",
|
|
299
|
+
"stroke-linejoin": "round"
|
|
300
|
+
) do
|
|
301
|
+
tag.path(d: "m15 18-6-6 6-6")
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def chevron_right_icon
|
|
306
|
+
content_tag(:svg,
|
|
307
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
308
|
+
width: "16",
|
|
309
|
+
height: "16",
|
|
310
|
+
viewBox: "0 0 24 24",
|
|
311
|
+
fill: "none",
|
|
312
|
+
stroke: "currentColor",
|
|
313
|
+
"stroke-width": "2",
|
|
314
|
+
"stroke-linecap": "round",
|
|
315
|
+
"stroke-linejoin": "round"
|
|
316
|
+
) do
|
|
317
|
+
tag.path(d: "m9 18 6-6-6-6")
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def calendar_attributes
|
|
322
|
+
{
|
|
323
|
+
class: "p-0",
|
|
324
|
+
role: "grid",
|
|
325
|
+
"aria-label": "Calendar",
|
|
326
|
+
data: {
|
|
327
|
+
"shadcn--date-picker-month-value": @month.iso8601,
|
|
328
|
+
"shadcn--date-picker-selected-value": @selected&.iso8601,
|
|
329
|
+
"shadcn--date-picker-format-value": @format.to_s,
|
|
330
|
+
"shadcn--date-picker-placeholder-value": @placeholder
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def picker_attributes
|
|
336
|
+
{
|
|
337
|
+
class: cn("relative inline-block", class_name),
|
|
338
|
+
data: stimulus_data
|
|
339
|
+
}.merge(html_options).merge(build_data)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def stimulus_data
|
|
343
|
+
data = {
|
|
344
|
+
controller: "shadcn--date-picker",
|
|
345
|
+
"shadcn--date-picker-open-value": "false",
|
|
346
|
+
"shadcn--date-picker-month-value": @month.iso8601,
|
|
347
|
+
"shadcn--date-picker-selected-value": @selected&.iso8601,
|
|
348
|
+
"shadcn--date-picker-format-value": @format.to_s,
|
|
349
|
+
"shadcn--date-picker-placeholder-value": @placeholder,
|
|
350
|
+
"shadcn--date-picker-show-outside-days-value": @show_outside_days.to_s,
|
|
351
|
+
"shadcn--date-picker-week-starts-on-value": @week_starts_on.to_s,
|
|
352
|
+
action: "keydown.escape->shadcn--date-picker#close click@window->shadcn--date-picker#closeOnClickOutside"
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
# Add optional values only if present
|
|
356
|
+
data["shadcn--date-picker-min-date-value"] = @min_date.iso8601 if @min_date
|
|
357
|
+
data["shadcn--date-picker-max-date-value"] = @max_date.iso8601 if @max_date
|
|
358
|
+
data["shadcn--date-picker-disabled-dates-value"] = format_disabled_dates if @disabled_dates.any?
|
|
359
|
+
data["shadcn--date-picker-disabled-days-of-week-value"] = @disabled_days_of_week.join(",") if @disabled_days_of_week.any?
|
|
360
|
+
|
|
361
|
+
data
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def format_disabled_dates
|
|
365
|
+
@disabled_dates.map(&:iso8601).join(",")
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Dialog component for modal windows
|
|
5
|
+
# Matches shadcn/ui Dialog component
|
|
6
|
+
# Uses Stimulus for interactivity
|
|
7
|
+
#
|
|
8
|
+
# @example Basic dialog
|
|
9
|
+
# <%= render Shadcn::DialogComponent.new do |dialog| %>
|
|
10
|
+
# <% dialog.with_trigger do %>
|
|
11
|
+
# <%= render Shadcn::ButtonComponent.new(variant: :outline) { "Open Dialog" } %>
|
|
12
|
+
# <% end %>
|
|
13
|
+
# <% dialog.with_content do |content| %>
|
|
14
|
+
# <% content.with_header do %>
|
|
15
|
+
# <% content.with_title { "Dialog Title" } %>
|
|
16
|
+
# <% content.with_description { "Dialog description here." } %>
|
|
17
|
+
# <% end %>
|
|
18
|
+
# <p>Dialog body content</p>
|
|
19
|
+
# <% content.with_footer do %>
|
|
20
|
+
# <%= render Shadcn::ButtonComponent.new { "Save" } %>
|
|
21
|
+
# <% end %>
|
|
22
|
+
# <% end %>
|
|
23
|
+
# <% end %>
|
|
24
|
+
#
|
|
25
|
+
class DialogComponent < BaseComponent
|
|
26
|
+
renders_one :trigger
|
|
27
|
+
renders_one :body, lambda { |**options|
|
|
28
|
+
DialogContentComponent.new(**options)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# @param id [String] Unique identifier for the dialog (used for Turbo Stream targeting)
|
|
32
|
+
# @param open [Boolean] Whether dialog starts open
|
|
33
|
+
# @param modal [Boolean] Whether dialog is modal (traps focus, blocks interaction)
|
|
34
|
+
def initialize(id: nil, open: false, modal: true, **options)
|
|
35
|
+
super(**options)
|
|
36
|
+
@id = id
|
|
37
|
+
@open = open
|
|
38
|
+
@modal = modal
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def call
|
|
42
|
+
content_tag(:div, dialog_content, dialog_attributes)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def dialog_content
|
|
48
|
+
safe_join([
|
|
49
|
+
trigger_wrapper,
|
|
50
|
+
body
|
|
51
|
+
].compact)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def trigger_wrapper
|
|
55
|
+
return unless trigger
|
|
56
|
+
|
|
57
|
+
content_tag(:div, trigger, {
|
|
58
|
+
"data-shadcn--dialog-target": "trigger",
|
|
59
|
+
"data-action": "click->shadcn--dialog#open"
|
|
60
|
+
})
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def dialog_attributes
|
|
64
|
+
attrs = {
|
|
65
|
+
id: @id,
|
|
66
|
+
class: class_name,
|
|
67
|
+
"data-controller": "shadcn--dialog",
|
|
68
|
+
"data-shadcn--dialog-open-value": @open.to_s,
|
|
69
|
+
"data-shadcn--dialog-modal-value": @modal.to_s,
|
|
70
|
+
"data-dialog-id": @id
|
|
71
|
+
}
|
|
72
|
+
attrs.merge!(html_options)
|
|
73
|
+
attrs.merge!(build_data)
|
|
74
|
+
attrs.compact
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|