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,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Radio Group component for selecting one option from a set
|
|
5
|
+
# Uses native <input type="radio"> elements styled with CSS
|
|
6
|
+
# Works without JavaScript for progressive enhancement
|
|
7
|
+
#
|
|
8
|
+
# @example Data-driven usage (Tier 1)
|
|
9
|
+
# <%= render Shadcn::RadioGroupComponent.new(
|
|
10
|
+
# name: "plan",
|
|
11
|
+
# value: "pro",
|
|
12
|
+
# items: [
|
|
13
|
+
# { value: "free", label: "Free" },
|
|
14
|
+
# { value: "pro", label: "Pro" },
|
|
15
|
+
# { value: "enterprise", label: "Enterprise", disabled: true }
|
|
16
|
+
# ]
|
|
17
|
+
# ) %>
|
|
18
|
+
#
|
|
19
|
+
# @example Simple DSL with labels (Tier 2)
|
|
20
|
+
# <%= render Shadcn::RadioGroupComponent.new(name: "plan") do |group| %>
|
|
21
|
+
# <%= group.with_item(value: "free", label: "Free") %>
|
|
22
|
+
# <%= group.with_item(value: "pro", label: "Pro") %>
|
|
23
|
+
# <% end %>
|
|
24
|
+
#
|
|
25
|
+
# @example With separate labels (backward compatible)
|
|
26
|
+
# <%= render Shadcn::RadioGroupComponent.new(name: "plan") do |group| %>
|
|
27
|
+
# <div class="flex items-center space-x-2">
|
|
28
|
+
# <%= group.with_item(value: "free", id: "free") %>
|
|
29
|
+
# <%= render Shadcn::LabelComponent.new(for: "free") { "Free" } %>
|
|
30
|
+
# </div>
|
|
31
|
+
# <% end %>
|
|
32
|
+
#
|
|
33
|
+
class RadioGroupComponent < BaseComponent
|
|
34
|
+
BASE_CLASSES = "grid gap-3"
|
|
35
|
+
|
|
36
|
+
renders_many :items, ->(value:, id: nil, label: nil, disabled: false, **options, &block) do
|
|
37
|
+
RadioGroupItemComponent.new(
|
|
38
|
+
value: value,
|
|
39
|
+
id: id || generate_item_id(value),
|
|
40
|
+
label: label,
|
|
41
|
+
disabled: @disabled || disabled,
|
|
42
|
+
group_name: @name,
|
|
43
|
+
selected: @value.to_s == value.to_s,
|
|
44
|
+
**options,
|
|
45
|
+
&block
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param name [String] Input name attribute for form submission
|
|
50
|
+
# @param value [String, nil] Currently selected value
|
|
51
|
+
# @param items [Array<Hash>] Array of item hashes with :value, :label, :disabled keys
|
|
52
|
+
# @param disabled [Boolean] Whether the entire group is disabled
|
|
53
|
+
# @param required [Boolean] Whether a selection is required
|
|
54
|
+
# @param orientation [Symbol] :vertical or :horizontal layout
|
|
55
|
+
def initialize(
|
|
56
|
+
name:,
|
|
57
|
+
value: nil,
|
|
58
|
+
items: [],
|
|
59
|
+
disabled: false,
|
|
60
|
+
required: false,
|
|
61
|
+
orientation: :vertical,
|
|
62
|
+
**options
|
|
63
|
+
)
|
|
64
|
+
super(**options)
|
|
65
|
+
@name = name
|
|
66
|
+
@value = value
|
|
67
|
+
@items_data = items
|
|
68
|
+
@disabled = disabled
|
|
69
|
+
@required = required
|
|
70
|
+
@orientation = orientation
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def call
|
|
74
|
+
content_tag(:div, group_attributes) do
|
|
75
|
+
safe_join([render_data_items, render_slot_items].compact)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
# Render items from the items: data array
|
|
82
|
+
def render_data_items
|
|
83
|
+
return nil if @items_data.empty?
|
|
84
|
+
|
|
85
|
+
safe_join(@items_data.map do |item|
|
|
86
|
+
RadioGroupItemComponent.new(
|
|
87
|
+
value: item[:value],
|
|
88
|
+
id: generate_item_id(item[:value]),
|
|
89
|
+
label: item[:label],
|
|
90
|
+
disabled: @disabled || item[:disabled],
|
|
91
|
+
group_name: @name,
|
|
92
|
+
selected: @value.to_s == item[:value].to_s
|
|
93
|
+
).render_in(view_context)
|
|
94
|
+
end)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Render items from with_item slot calls
|
|
98
|
+
def render_slot_items
|
|
99
|
+
return nil if items.empty?
|
|
100
|
+
|
|
101
|
+
safe_join(items.map(&:to_s))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def generate_item_id(value)
|
|
105
|
+
"#{@name}-#{value}".parameterize
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def group_attributes
|
|
109
|
+
attrs = {
|
|
110
|
+
role: "radiogroup",
|
|
111
|
+
class: cn(BASE_CLASSES, orientation_classes, class_name),
|
|
112
|
+
"aria-required": @required ? "true" : nil,
|
|
113
|
+
"aria-disabled": @disabled ? "true" : nil
|
|
114
|
+
}
|
|
115
|
+
attrs.merge!(html_options)
|
|
116
|
+
attrs.merge!(build_data)
|
|
117
|
+
attrs.compact
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def orientation_classes
|
|
121
|
+
case @orientation
|
|
122
|
+
when :horizontal
|
|
123
|
+
"flex flex-row gap-4"
|
|
124
|
+
else
|
|
125
|
+
"grid gap-3"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Individual radio button item using native <input type="radio">
|
|
5
|
+
# Styled with CSS to match shadcn/ui design
|
|
6
|
+
# Works without JavaScript
|
|
7
|
+
#
|
|
8
|
+
# @example With label parameter (Tier 2 API)
|
|
9
|
+
# <%= group.with_item(value: "free", label: "Free") %>
|
|
10
|
+
#
|
|
11
|
+
# @example With block content (backward compatible)
|
|
12
|
+
# <%= group.with_item(value: "free") { "Free" } %>
|
|
13
|
+
#
|
|
14
|
+
# @example Without label (for external labels)
|
|
15
|
+
# <%= group.with_item(value: "free", id: "plan-free") %>
|
|
16
|
+
#
|
|
17
|
+
class RadioGroupItemComponent < BaseComponent
|
|
18
|
+
# CSS classes for the native radio input styled as a custom circle
|
|
19
|
+
ITEM_CLASSES = [
|
|
20
|
+
# Reset native appearance
|
|
21
|
+
"appearance-none",
|
|
22
|
+
# Size and shape
|
|
23
|
+
"aspect-square h-4 w-4 shrink-0 rounded-full",
|
|
24
|
+
# Border and colors
|
|
25
|
+
"border border-primary",
|
|
26
|
+
# Ring focus style
|
|
27
|
+
"focus:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1",
|
|
28
|
+
# Disabled state
|
|
29
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
30
|
+
# Custom checked indicator using CSS
|
|
31
|
+
"relative",
|
|
32
|
+
# Checked state - show inner circle
|
|
33
|
+
"checked:bg-primary",
|
|
34
|
+
# Transition for smooth state changes
|
|
35
|
+
"transition-colors"
|
|
36
|
+
].join(" ")
|
|
37
|
+
|
|
38
|
+
LABEL_CLASSES = "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
39
|
+
|
|
40
|
+
# @param value [String] The value for this radio option
|
|
41
|
+
# @param id [String, nil] HTML id attribute
|
|
42
|
+
# @param label [String, nil] Label text (alternative to block content)
|
|
43
|
+
# @param disabled [Boolean] Whether this option is disabled
|
|
44
|
+
# @param group_name [String, nil] The name attribute from parent group
|
|
45
|
+
# @param selected [Boolean] Whether this option is selected
|
|
46
|
+
def initialize(
|
|
47
|
+
value:,
|
|
48
|
+
id: nil,
|
|
49
|
+
label: nil,
|
|
50
|
+
disabled: false,
|
|
51
|
+
group_name: nil,
|
|
52
|
+
selected: false,
|
|
53
|
+
**options,
|
|
54
|
+
&block
|
|
55
|
+
)
|
|
56
|
+
super(**options, &block)
|
|
57
|
+
@value = value
|
|
58
|
+
@id = id || "radio-#{value}"
|
|
59
|
+
@label = label
|
|
60
|
+
@disabled = disabled
|
|
61
|
+
@group_name = group_name
|
|
62
|
+
@selected = selected
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def call
|
|
66
|
+
label_text = @label || content.presence
|
|
67
|
+
|
|
68
|
+
if label_text.present?
|
|
69
|
+
# Render with integrated label
|
|
70
|
+
content_tag(:label, label_wrapper_attributes) do
|
|
71
|
+
safe_join([
|
|
72
|
+
radio_input,
|
|
73
|
+
content_tag(:span, label_text, class: LABEL_CLASSES)
|
|
74
|
+
])
|
|
75
|
+
end
|
|
76
|
+
else
|
|
77
|
+
# Render just the radio input (for use with external labels)
|
|
78
|
+
radio_input
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def radio_input
|
|
85
|
+
tag(:input, input_attributes)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def input_attributes
|
|
89
|
+
attrs = {
|
|
90
|
+
type: "radio",
|
|
91
|
+
name: @group_name,
|
|
92
|
+
value: @value,
|
|
93
|
+
id: @id,
|
|
94
|
+
class: cn(ITEM_CLASSES, "peer", class_name),
|
|
95
|
+
disabled: @disabled || nil,
|
|
96
|
+
checked: @selected || nil
|
|
97
|
+
}
|
|
98
|
+
attrs.merge!(html_options.except(:class))
|
|
99
|
+
attrs.compact
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def label_wrapper_attributes
|
|
103
|
+
{
|
|
104
|
+
class: "flex items-center space-x-2 cursor-pointer",
|
|
105
|
+
for: @id
|
|
106
|
+
}
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Draggable handle between resizable panels
|
|
5
|
+
class ResizableHandleComponent < BaseComponent
|
|
6
|
+
HORIZONTAL_CLASSES = "w-px bg-border cursor-col-resize hover:bg-primary/50 transition-colors"
|
|
7
|
+
VERTICAL_CLASSES = "h-px bg-border cursor-row-resize hover:bg-primary/50 transition-colors"
|
|
8
|
+
|
|
9
|
+
# @param with_handle [Boolean] Whether to show a visual handle indicator
|
|
10
|
+
# @param direction [Symbol] Direction passed from parent group
|
|
11
|
+
def initialize(with_handle: false, direction: :horizontal, **options)
|
|
12
|
+
super(**options)
|
|
13
|
+
@with_handle = with_handle
|
|
14
|
+
@direction = direction
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
content_tag(:div, handle_content, handle_attributes)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def handle_content
|
|
24
|
+
return nil unless @with_handle
|
|
25
|
+
|
|
26
|
+
content_tag(:div, grip_icon, handle_indicator_attributes)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def handle_attributes
|
|
30
|
+
{
|
|
31
|
+
class: class_names,
|
|
32
|
+
"data-panel-resize-handle": "",
|
|
33
|
+
"data-shadcn--resizable-target": "handle",
|
|
34
|
+
"data-action": "mousedown->shadcn--resizable#startResize touchstart->shadcn--resizable#startResize",
|
|
35
|
+
role: "separator",
|
|
36
|
+
tabindex: "0",
|
|
37
|
+
"aria-orientation": @direction == :horizontal ? "vertical" : "horizontal"
|
|
38
|
+
}.merge(html_options).compact
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def handle_indicator_attributes
|
|
42
|
+
{
|
|
43
|
+
class: cn(
|
|
44
|
+
"z-10 flex items-center justify-center rounded-sm border bg-border",
|
|
45
|
+
@direction == :horizontal ? "h-4 w-3" : "w-4 h-3"
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def grip_icon
|
|
51
|
+
if @direction == :horizontal
|
|
52
|
+
# Vertical grip dots for horizontal resize
|
|
53
|
+
content_tag(:svg, grip_dots_vertical, xmlns: "http://www.w3.org/2000/svg", width: "10", height: "16", viewBox: "0 0 10 16", fill: "currentColor", class: "h-2.5 w-2.5")
|
|
54
|
+
else
|
|
55
|
+
# Horizontal grip dots for vertical resize
|
|
56
|
+
content_tag(:svg, grip_dots_horizontal, xmlns: "http://www.w3.org/2000/svg", width: "16", height: "10", viewBox: "0 0 16 10", fill: "currentColor", class: "h-2.5 w-2.5")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def grip_dots_vertical
|
|
61
|
+
# GripVertical icon - 6 dots in 2 columns
|
|
62
|
+
safe_join([
|
|
63
|
+
content_tag(:circle, nil, cx: "3", cy: "2", r: "1"),
|
|
64
|
+
content_tag(:circle, nil, cx: "7", cy: "2", r: "1"),
|
|
65
|
+
content_tag(:circle, nil, cx: "3", cy: "8", r: "1"),
|
|
66
|
+
content_tag(:circle, nil, cx: "7", cy: "8", r: "1"),
|
|
67
|
+
content_tag(:circle, nil, cx: "3", cy: "14", r: "1"),
|
|
68
|
+
content_tag(:circle, nil, cx: "7", cy: "14", r: "1")
|
|
69
|
+
])
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def grip_dots_horizontal
|
|
73
|
+
# GripHorizontal icon - 6 dots in 2 rows
|
|
74
|
+
safe_join([
|
|
75
|
+
content_tag(:circle, nil, cx: "2", cy: "3", r: "1"),
|
|
76
|
+
content_tag(:circle, nil, cx: "8", cy: "3", r: "1"),
|
|
77
|
+
content_tag(:circle, nil, cx: "14", cy: "3", r: "1"),
|
|
78
|
+
content_tag(:circle, nil, cx: "2", cy: "7", r: "1"),
|
|
79
|
+
content_tag(:circle, nil, cx: "8", cy: "7", r: "1"),
|
|
80
|
+
content_tag(:circle, nil, cx: "14", cy: "7", r: "1")
|
|
81
|
+
])
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def class_names
|
|
85
|
+
base = @direction == :horizontal ? HORIZONTAL_CLASSES : VERTICAL_CLASSES
|
|
86
|
+
cn(
|
|
87
|
+
"relative flex items-center justify-center",
|
|
88
|
+
base,
|
|
89
|
+
@with_handle && (@direction == :horizontal ? "w-2" : "h-2"),
|
|
90
|
+
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1",
|
|
91
|
+
"after:absolute",
|
|
92
|
+
@direction == :horizontal ? "after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2" : "after:inset-x-0 after:top-1/2 after:h-1 after:-translate-y-1/2",
|
|
93
|
+
"[&[data-state=dragging]]:bg-primary",
|
|
94
|
+
@class_name
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Individual panel within a ResizablePanelGroup
|
|
5
|
+
class ResizablePanelComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "relative"
|
|
7
|
+
|
|
8
|
+
# @param default_size [Integer] Initial size percentage (0-100)
|
|
9
|
+
# @param min_size [Integer] Minimum size percentage
|
|
10
|
+
# @param max_size [Integer] Maximum size percentage
|
|
11
|
+
# @param direction [Symbol] Direction passed from parent group
|
|
12
|
+
def initialize(default_size: nil, min_size: nil, max_size: nil, direction: :horizontal, **options)
|
|
13
|
+
super(**options)
|
|
14
|
+
@default_size = default_size
|
|
15
|
+
@min_size = min_size
|
|
16
|
+
@max_size = max_size
|
|
17
|
+
@direction = direction
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call
|
|
21
|
+
content_tag(:div, content, panel_attributes)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def panel_attributes
|
|
27
|
+
{
|
|
28
|
+
class: class_names,
|
|
29
|
+
style: panel_style,
|
|
30
|
+
"data-panel": "",
|
|
31
|
+
"data-panel-size": @default_size,
|
|
32
|
+
"data-shadcn--resizable-target": "panel",
|
|
33
|
+
"data-min-size": @min_size,
|
|
34
|
+
"data-max-size": @max_size
|
|
35
|
+
}.merge(html_options).compact
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def panel_style
|
|
39
|
+
return unless @default_size
|
|
40
|
+
|
|
41
|
+
if @direction == :horizontal
|
|
42
|
+
"flex-basis: #{@default_size}%; flex-grow: 0; flex-shrink: 0;"
|
|
43
|
+
else
|
|
44
|
+
"flex-basis: #{@default_size}%; flex-grow: 0; flex-shrink: 0;"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def class_names
|
|
49
|
+
cn(
|
|
50
|
+
BASE_CLASSES,
|
|
51
|
+
"overflow-hidden",
|
|
52
|
+
@class_name
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# ResizablePanelGroup component for creating resizable panel layouts
|
|
5
|
+
# Matches shadcn/ui Resizable component
|
|
6
|
+
# Uses Stimulus for interactivity
|
|
7
|
+
#
|
|
8
|
+
# @example Horizontal layout
|
|
9
|
+
# <%= render Shadcn::ResizablePanelGroupComponent.new(direction: :horizontal) do |group| %>
|
|
10
|
+
# <% group.with_panel(default_size: 50) do %>
|
|
11
|
+
# <div class="flex h-full items-center justify-center p-6">
|
|
12
|
+
# <span class="font-semibold">One</span>
|
|
13
|
+
# </div>
|
|
14
|
+
# <% end %>
|
|
15
|
+
# <% group.with_handle %>
|
|
16
|
+
# <% group.with_panel(default_size: 50) do %>
|
|
17
|
+
# <div class="flex h-full items-center justify-center p-6">
|
|
18
|
+
# <span class="font-semibold">Two</span>
|
|
19
|
+
# </div>
|
|
20
|
+
# <% end %>
|
|
21
|
+
# <% end %>
|
|
22
|
+
#
|
|
23
|
+
# @example Vertical layout
|
|
24
|
+
# <%= render Shadcn::ResizablePanelGroupComponent.new(direction: :vertical) do |group| %>
|
|
25
|
+
# <% group.with_panel(default_size: 25) do %>
|
|
26
|
+
# Header
|
|
27
|
+
# <% end %>
|
|
28
|
+
# <% group.with_handle %>
|
|
29
|
+
# <% group.with_panel(default_size: 75) do %>
|
|
30
|
+
# Content
|
|
31
|
+
# <% end %>
|
|
32
|
+
# <% end %>
|
|
33
|
+
#
|
|
34
|
+
class ResizablePanelGroupComponent < BaseComponent
|
|
35
|
+
renders_many :panels, lambda { |default_size: nil, min_size: nil, max_size: nil, **options|
|
|
36
|
+
ResizablePanelComponent.new(
|
|
37
|
+
default_size: default_size,
|
|
38
|
+
min_size: min_size,
|
|
39
|
+
max_size: max_size,
|
|
40
|
+
direction: @direction,
|
|
41
|
+
**options
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
renders_many :handles, lambda { |with_handle: false, **options|
|
|
46
|
+
ResizableHandleComponent.new(
|
|
47
|
+
with_handle: with_handle,
|
|
48
|
+
direction: @direction,
|
|
49
|
+
**options
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
DIRECTIONS = {
|
|
54
|
+
horizontal: "flex h-full",
|
|
55
|
+
vertical: "flex flex-col"
|
|
56
|
+
}.freeze
|
|
57
|
+
|
|
58
|
+
# @param direction [Symbol] Direction of the panels (:horizontal, :vertical)
|
|
59
|
+
# @param auto_save_id [String] ID for persisting panel sizes to localStorage
|
|
60
|
+
def initialize(direction: :horizontal, auto_save_id: nil, **options)
|
|
61
|
+
super(**options)
|
|
62
|
+
@direction = direction.to_sym
|
|
63
|
+
@auto_save_id = auto_save_id
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def call
|
|
67
|
+
content_tag(:div, group_content, group_attributes)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def group_content
|
|
73
|
+
content
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def group_attributes
|
|
77
|
+
{
|
|
78
|
+
class: class_names,
|
|
79
|
+
"data-controller": "shadcn--resizable",
|
|
80
|
+
"data-shadcn--resizable-direction-value": @direction.to_s,
|
|
81
|
+
"data-shadcn--resizable-auto-save-id-value": @auto_save_id,
|
|
82
|
+
"data-panel-group": "",
|
|
83
|
+
"data-panel-group-direction": @direction.to_s
|
|
84
|
+
}.merge(html_options).compact
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def class_names
|
|
88
|
+
cn(
|
|
89
|
+
DIRECTIONS[@direction],
|
|
90
|
+
@class_name
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Scroll Area component for custom scrollbars
|
|
5
|
+
# Matches shadcn/ui ScrollArea component
|
|
6
|
+
# Uses Stimulus for interactivity
|
|
7
|
+
#
|
|
8
|
+
# @example Basic scroll area
|
|
9
|
+
# <%= render Shadcn::ScrollAreaComponent.new(class_name: "h-72 w-48") do %>
|
|
10
|
+
# <div class="p-4">
|
|
11
|
+
# Long content here...
|
|
12
|
+
# </div>
|
|
13
|
+
# <% end %>
|
|
14
|
+
#
|
|
15
|
+
# @example Horizontal scroll
|
|
16
|
+
# <%= render Shadcn::ScrollAreaComponent.new(orientation: :horizontal, class_name: "w-96 whitespace-nowrap") do %>
|
|
17
|
+
# <div class="flex space-x-4 p-4">
|
|
18
|
+
# <!-- items -->
|
|
19
|
+
# </div>
|
|
20
|
+
# <% end %>
|
|
21
|
+
#
|
|
22
|
+
class ScrollAreaComponent < BaseComponent
|
|
23
|
+
BASE_CLASSES = "relative overflow-hidden"
|
|
24
|
+
VIEWPORT_CLASSES = "h-full w-full rounded-[inherit]"
|
|
25
|
+
SCROLLBAR_CLASSES = "flex touch-none select-none transition-colors"
|
|
26
|
+
THUMB_CLASSES = "relative flex-1 rounded-full bg-border"
|
|
27
|
+
|
|
28
|
+
# @param orientation [Symbol] Scroll orientation (:vertical, :horizontal, :both)
|
|
29
|
+
# @param type [Symbol] Scrollbar type (:auto, :always, :scroll, :hover)
|
|
30
|
+
def initialize(orientation: :vertical, type: :hover, **options)
|
|
31
|
+
super(**options)
|
|
32
|
+
@orientation = orientation.to_sym
|
|
33
|
+
@type = type.to_sym
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def call
|
|
37
|
+
content_tag(:div, scroll_structure, scroll_attributes)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def scroll_structure
|
|
43
|
+
safe_join([
|
|
44
|
+
viewport,
|
|
45
|
+
scrollbars,
|
|
46
|
+
corner
|
|
47
|
+
])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def viewport
|
|
51
|
+
content_tag(:div, content, {
|
|
52
|
+
class: cn(VIEWPORT_CLASSES, @orientation == :horizontal ? "overflow-x-auto" : "overflow-y-auto"),
|
|
53
|
+
style: "overflow: hidden scroll;",
|
|
54
|
+
"data-shadcn--scroll-area-target": "viewport"
|
|
55
|
+
})
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def scrollbars
|
|
59
|
+
case @orientation
|
|
60
|
+
when :both
|
|
61
|
+
safe_join([vertical_scrollbar, horizontal_scrollbar])
|
|
62
|
+
when :horizontal
|
|
63
|
+
horizontal_scrollbar
|
|
64
|
+
else
|
|
65
|
+
vertical_scrollbar
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def vertical_scrollbar
|
|
70
|
+
content_tag(:div, scrollbar_thumb, {
|
|
71
|
+
class: cn(SCROLLBAR_CLASSES, "h-full w-2.5 border-l border-l-transparent p-[1px]"),
|
|
72
|
+
"data-orientation": "vertical",
|
|
73
|
+
"data-shadcn--scroll-area-target": "scrollbar"
|
|
74
|
+
})
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def horizontal_scrollbar
|
|
78
|
+
content_tag(:div, scrollbar_thumb, {
|
|
79
|
+
class: cn(SCROLLBAR_CLASSES, "h-2.5 flex-col border-t border-t-transparent p-[1px]"),
|
|
80
|
+
"data-orientation": "horizontal",
|
|
81
|
+
"data-shadcn--scroll-area-target": "scrollbar"
|
|
82
|
+
})
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def scrollbar_thumb
|
|
86
|
+
content_tag(:div, "", {
|
|
87
|
+
class: THUMB_CLASSES,
|
|
88
|
+
"data-shadcn--scroll-area-target": "thumb"
|
|
89
|
+
})
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def corner
|
|
93
|
+
return unless @orientation == :both
|
|
94
|
+
|
|
95
|
+
content_tag(:div, "", class: "absolute right-0 bottom-0 h-2.5 w-2.5 bg-transparent")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def scroll_attributes
|
|
99
|
+
attrs = {
|
|
100
|
+
class: merge_classes(BASE_CLASSES),
|
|
101
|
+
"data-controller": "shadcn--scroll-area",
|
|
102
|
+
"data-shadcn--scroll-area-orientation-value": @orientation.to_s,
|
|
103
|
+
"data-shadcn--scroll-area-type-value": @type.to_s
|
|
104
|
+
}
|
|
105
|
+
attrs.merge!(html_options)
|
|
106
|
+
attrs.merge!(build_data)
|
|
107
|
+
attrs.compact
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|