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,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Aspect Ratio component for maintaining content proportions
|
|
5
|
+
# Matches shadcn/ui AspectRatio component
|
|
6
|
+
#
|
|
7
|
+
# @example 16:9 video ratio
|
|
8
|
+
# <%= render Shadcn::AspectRatioComponent.new(ratio: 16.0/9.0) do %>
|
|
9
|
+
# <img src="image.jpg" class="h-full w-full object-cover" />
|
|
10
|
+
# <% end %>
|
|
11
|
+
#
|
|
12
|
+
# @example Square ratio
|
|
13
|
+
# <%= render Shadcn::AspectRatioComponent.new(ratio: 1) do %>
|
|
14
|
+
# <img src="square.jpg" />
|
|
15
|
+
# <% end %>
|
|
16
|
+
#
|
|
17
|
+
class AspectRatioComponent < BaseComponent
|
|
18
|
+
# @param ratio [Float] Aspect ratio (width/height, e.g. 16.0/9.0 for widescreen)
|
|
19
|
+
def initialize(ratio: 1, **options)
|
|
20
|
+
super(**options)
|
|
21
|
+
@ratio = ratio.to_f
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call
|
|
25
|
+
content_tag(:div, wrapper_attributes) do
|
|
26
|
+
content_tag(:div, content, inner_attributes)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def wrapper_attributes
|
|
33
|
+
attrs = {
|
|
34
|
+
class: cn("relative w-full", class_name),
|
|
35
|
+
style: "padding-bottom: #{(1.0 / @ratio) * 100}%;"
|
|
36
|
+
}
|
|
37
|
+
attrs.merge!(html_options)
|
|
38
|
+
attrs.merge!(build_data)
|
|
39
|
+
attrs.compact
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def inner_attributes
|
|
43
|
+
{
|
|
44
|
+
class: "absolute inset-0",
|
|
45
|
+
style: "position: absolute; top: 0; right: 0; bottom: 0; left: 0;"
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Avatar component for user profile images
|
|
5
|
+
# Matches shadcn/ui Avatar component
|
|
6
|
+
#
|
|
7
|
+
# @example Basic avatar with image
|
|
8
|
+
# <%= render Shadcn::AvatarComponent.new(src: user.avatar_url, alt: user.name) %>
|
|
9
|
+
#
|
|
10
|
+
# @example With fallback
|
|
11
|
+
# <%= render Shadcn::AvatarComponent.new(src: user.avatar_url, alt: user.name, fallback: user.initials) %>
|
|
12
|
+
#
|
|
13
|
+
# @example Different sizes
|
|
14
|
+
# <%= render Shadcn::AvatarComponent.new(src: url, alt: name, size: :sm) %>
|
|
15
|
+
# <%= render Shadcn::AvatarComponent.new(src: url, alt: name, size: :lg) %>
|
|
16
|
+
#
|
|
17
|
+
# @example With slot-based fallback
|
|
18
|
+
# <%= render Shadcn::AvatarComponent.new(size: :sm) do |avatar| %>
|
|
19
|
+
# <% avatar.with_fallback { "JD" } %>
|
|
20
|
+
# <% end %>
|
|
21
|
+
#
|
|
22
|
+
class AvatarComponent < BaseComponent
|
|
23
|
+
renders_one :fallback, ->(class: nil, **options, &block) {
|
|
24
|
+
AvatarFallbackComponent.new(class: binding.local_variable_get(:class), **options, &block)
|
|
25
|
+
}
|
|
26
|
+
SIZES = {
|
|
27
|
+
sm: "h-8 w-8 text-xs",
|
|
28
|
+
default: "h-10 w-10 text-sm",
|
|
29
|
+
lg: "h-12 w-12 text-base",
|
|
30
|
+
xl: "h-16 w-16 text-lg"
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
BASE_CLASSES = "relative flex shrink-0 overflow-hidden rounded-full"
|
|
34
|
+
IMAGE_CLASSES = "aspect-square h-full w-full"
|
|
35
|
+
FALLBACK_CLASSES = "flex h-full w-full items-center justify-center rounded-full bg-muted"
|
|
36
|
+
|
|
37
|
+
# @param src [String, nil] Image URL
|
|
38
|
+
# @param alt [String] Alt text for the image
|
|
39
|
+
# @param fallback [String, nil] Fallback text when image fails to load
|
|
40
|
+
# @param size [Symbol] Avatar size (:sm, :default, :lg, :xl)
|
|
41
|
+
def initialize(src: nil, alt: "", fallback: nil, size: :default, **options)
|
|
42
|
+
super(**options)
|
|
43
|
+
@src = src
|
|
44
|
+
@alt = alt
|
|
45
|
+
@fallback = fallback || generate_fallback(alt)
|
|
46
|
+
@size = size.to_sym
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def call
|
|
50
|
+
content_tag(:span, avatar_content, avatar_attributes)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def avatar_content
|
|
56
|
+
if @src.present?
|
|
57
|
+
image_with_fallback
|
|
58
|
+
elsif fallback?
|
|
59
|
+
fallback
|
|
60
|
+
else
|
|
61
|
+
fallback_element
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def image_with_fallback
|
|
66
|
+
# Use Stimulus controller to handle image loading errors
|
|
67
|
+
content_tag(:span, class: "contents", data: stimulus_data(controller: "shadcn--avatar")) do
|
|
68
|
+
safe_join([
|
|
69
|
+
tag(:img,
|
|
70
|
+
src: @src,
|
|
71
|
+
alt: @alt,
|
|
72
|
+
class: IMAGE_CLASSES,
|
|
73
|
+
data: { "shadcn--avatar-target": "image", action: "error->shadcn--avatar#handleError" }
|
|
74
|
+
),
|
|
75
|
+
content_tag(:span, @fallback, class: "#{FALLBACK_CLASSES} hidden", data: { "shadcn--avatar-target": "fallback" })
|
|
76
|
+
])
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def fallback_element
|
|
81
|
+
content_tag(:span, @fallback, class: FALLBACK_CLASSES)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def avatar_classes
|
|
85
|
+
cn(BASE_CLASSES, SIZES[@size], class_name)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def avatar_attributes
|
|
89
|
+
attrs = { class: avatar_classes }
|
|
90
|
+
attrs.merge!(html_options)
|
|
91
|
+
attrs.merge!(build_data)
|
|
92
|
+
attrs.compact
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def generate_fallback(alt)
|
|
96
|
+
return "" if alt.blank?
|
|
97
|
+
|
|
98
|
+
# Generate initials from name
|
|
99
|
+
words = alt.split
|
|
100
|
+
if words.length >= 2
|
|
101
|
+
"#{words.first[0]}#{words.last[0]}".upcase
|
|
102
|
+
else
|
|
103
|
+
alt[0..1].upcase
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Avatar Fallback component for initials or placeholder
|
|
5
|
+
class AvatarFallbackComponent < BaseComponent
|
|
6
|
+
FALLBACK_CLASSES = "flex h-full w-full items-center justify-center rounded-full bg-muted"
|
|
7
|
+
|
|
8
|
+
def initialize(class: nil, **options, &block)
|
|
9
|
+
super(**options, &block)
|
|
10
|
+
@custom_class = binding.local_variable_get(:class)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
content_tag(:span, content, class: cn(FALLBACK_CLASSES, @custom_class))
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Badge component for labels and status indicators
|
|
5
|
+
# Matches shadcn/ui Badge component
|
|
6
|
+
#
|
|
7
|
+
# @example Basic badge
|
|
8
|
+
# <%= render Shadcn::BadgeComponent.new { "New" } %>
|
|
9
|
+
#
|
|
10
|
+
# @example Variant badges
|
|
11
|
+
# <%= render Shadcn::BadgeComponent.new(variant: :secondary) { "Draft" } %>
|
|
12
|
+
# <%= render Shadcn::BadgeComponent.new(variant: :destructive) { "Error" } %>
|
|
13
|
+
# <%= render Shadcn::BadgeComponent.new(variant: :outline) { "v1.0.0" } %>
|
|
14
|
+
#
|
|
15
|
+
class BadgeComponent < BaseComponent
|
|
16
|
+
# Available badge variants
|
|
17
|
+
VARIANTS = {
|
|
18
|
+
default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
|
|
19
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
20
|
+
destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
|
|
21
|
+
outline: "text-foreground"
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
BASE_CLASSES = "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
|
25
|
+
|
|
26
|
+
# @param variant [Symbol] Badge style variant (:default, :secondary, :destructive, :outline)
|
|
27
|
+
def initialize(variant: :default, **options)
|
|
28
|
+
super(**options)
|
|
29
|
+
@variant = variant.to_sym
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def call
|
|
33
|
+
content_tag(:span, content, badge_attributes)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def badge_classes
|
|
39
|
+
cn(BASE_CLASSES, VARIANTS[@variant], class_name)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def badge_attributes
|
|
43
|
+
attrs = { class: badge_classes }
|
|
44
|
+
attrs.merge!(html_options)
|
|
45
|
+
attrs.merge!(build_data)
|
|
46
|
+
attrs.compact
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Base class for all shadcn ViewComponents
|
|
5
|
+
# Provides common functionality like class merging and data attribute handling
|
|
6
|
+
class BaseComponent < ViewComponent::Base
|
|
7
|
+
# Include class name helper for cn() method
|
|
8
|
+
include Shadcn::Rails::Helpers::ClassNameHelper
|
|
9
|
+
|
|
10
|
+
# Common attributes shared by all components
|
|
11
|
+
attr_reader :class_name, :data, :html_options
|
|
12
|
+
|
|
13
|
+
# @param class_name [String, nil] Additional CSS classes
|
|
14
|
+
# @param data [Hash] Data attributes (will be prefixed with data-)
|
|
15
|
+
# @param html_options [Hash] Additional HTML attributes
|
|
16
|
+
def initialize(class_name: nil, data: {}, **html_options, &block)
|
|
17
|
+
@class_name = class_name
|
|
18
|
+
@data = data
|
|
19
|
+
@html_options = html_options
|
|
20
|
+
@constructor_block = block
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Override content to support blocks passed to new()
|
|
24
|
+
# This allows both syntaxes:
|
|
25
|
+
# render Component.new { "text" } # block to new()
|
|
26
|
+
# render Component.new do %>text<% end # block to render()
|
|
27
|
+
# Note: Only calls blocks with arity 0 (no arguments).
|
|
28
|
+
# Blocks expecting arguments (like slot blocks) are handled by ViewComponent.
|
|
29
|
+
def content
|
|
30
|
+
return super if super.present?
|
|
31
|
+
return @constructor_block.call if @constructor_block && @constructor_block.arity == 0
|
|
32
|
+
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
# Merge default classes with user-provided classes
|
|
39
|
+
# @param default_classes [String] Default component classes
|
|
40
|
+
# @return [String] Merged class string
|
|
41
|
+
def merge_classes(default_classes)
|
|
42
|
+
cn(default_classes, class_name)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Build data attributes hash
|
|
46
|
+
# Converts Ruby-style keys to HTML data attributes
|
|
47
|
+
# @param additional_data [Hash] Additional data attributes to merge
|
|
48
|
+
# @return [Hash] Merged data attributes
|
|
49
|
+
def build_data(additional_data = {})
|
|
50
|
+
merged = data.merge(additional_data)
|
|
51
|
+
merged.transform_keys { |key| "data-#{key.to_s.dasherize}" }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Build the complete HTML attributes hash
|
|
55
|
+
# @param default_classes [String] Default component classes
|
|
56
|
+
# @param additional_data [Hash] Additional data attributes
|
|
57
|
+
# @return [Hash] Complete HTML attributes
|
|
58
|
+
def build_html_attributes(default_classes, additional_data = {})
|
|
59
|
+
attrs = html_options.dup
|
|
60
|
+
attrs[:class] = merge_classes(default_classes)
|
|
61
|
+
attrs.merge!(build_data(additional_data))
|
|
62
|
+
attrs
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Helper to build Stimulus controller data attributes
|
|
66
|
+
# @param controller [String] Stimulus controller name
|
|
67
|
+
# @param values [Hash] Controller values
|
|
68
|
+
# @param actions [Hash] Controller actions
|
|
69
|
+
# @return [Hash] Stimulus data attributes
|
|
70
|
+
def stimulus_data(controller:, values: {}, actions: {}, targets: {})
|
|
71
|
+
data = { controller: controller }
|
|
72
|
+
|
|
73
|
+
values.each do |key, value|
|
|
74
|
+
data[:"#{controller}-#{key}-value"] = value
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
actions.each do |event, action|
|
|
78
|
+
data[:action] = [data[:action], "#{event}->#{controller}##{action}"].compact.join(" ")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
targets.each do |name, _|
|
|
82
|
+
data[:"#{controller}-target"] = name
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
data
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Access configuration
|
|
89
|
+
def config
|
|
90
|
+
Shadcn::Rails.configuration
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Add prefix to Tailwind classes if configured
|
|
94
|
+
def prefix_classes(classes)
|
|
95
|
+
return classes if config.tailwind_prefix.blank?
|
|
96
|
+
|
|
97
|
+
classes.split.map { |c| "#{config.tailwind_prefix}#{c}" }.join(" ")
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Breadcrumb component for navigation hierarchy
|
|
5
|
+
# Matches shadcn/ui Breadcrumb component
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage
|
|
8
|
+
# <%= render Shadcn::BreadcrumbComponent.new do |breadcrumb| %>
|
|
9
|
+
# <% breadcrumb.with_item(href: "/") { "Home" } %>
|
|
10
|
+
# <% breadcrumb.with_item(href: "/products") { "Products" } %>
|
|
11
|
+
# <% breadcrumb.with_item(current: true) { "Widget" } %>
|
|
12
|
+
# <% end %>
|
|
13
|
+
#
|
|
14
|
+
class BreadcrumbComponent < BaseComponent
|
|
15
|
+
renders_many :items, ->(href: nil, current: false, **options, &block) do
|
|
16
|
+
BreadcrumbItemComponent.new(
|
|
17
|
+
href: href,
|
|
18
|
+
current: current,
|
|
19
|
+
**options,
|
|
20
|
+
&block
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call
|
|
25
|
+
content_tag(:nav, breadcrumb_attributes) do
|
|
26
|
+
content_tag(:ol, class: "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5") do
|
|
27
|
+
safe_join(items_with_separators)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def breadcrumb_attributes
|
|
35
|
+
attrs = {
|
|
36
|
+
"aria-label": "Breadcrumb",
|
|
37
|
+
class: merge_classes("")
|
|
38
|
+
}
|
|
39
|
+
attrs.merge!(html_options)
|
|
40
|
+
attrs.merge!(build_data)
|
|
41
|
+
attrs.compact
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def items_with_separators
|
|
45
|
+
items.each_with_index.flat_map do |item, index|
|
|
46
|
+
result = [content_tag(:li, class: "inline-flex items-center gap-1.5") { item.to_s }]
|
|
47
|
+
result << separator unless index == items.length - 1
|
|
48
|
+
result
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def separator
|
|
53
|
+
content_tag(:li, role: "presentation", "aria-hidden": "true", class: "text-muted-foreground") do
|
|
54
|
+
content_tag(:svg,
|
|
55
|
+
content_tag(:path, nil, d: "m9 18 6-6-6-6"),
|
|
56
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
57
|
+
width: "16",
|
|
58
|
+
height: "16",
|
|
59
|
+
viewBox: "0 0 24 24",
|
|
60
|
+
fill: "none",
|
|
61
|
+
stroke: "currentColor",
|
|
62
|
+
"stroke-width": "2",
|
|
63
|
+
"stroke-linecap": "round",
|
|
64
|
+
"stroke-linejoin": "round",
|
|
65
|
+
class: "h-3.5 w-3.5"
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Individual breadcrumb item
|
|
5
|
+
class BreadcrumbItemComponent < BaseComponent
|
|
6
|
+
LINK_CLASSES = "transition-colors hover:text-foreground"
|
|
7
|
+
PAGE_CLASSES = "font-normal text-foreground"
|
|
8
|
+
|
|
9
|
+
def initialize(href: nil, current: false, class_name: nil, **options)
|
|
10
|
+
super(class_name: class_name, **options)
|
|
11
|
+
@href = href
|
|
12
|
+
@current = current
|
|
13
|
+
@class_name = class_name
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
if @current
|
|
18
|
+
content_tag(:span, item_attributes) do
|
|
19
|
+
content
|
|
20
|
+
end
|
|
21
|
+
else
|
|
22
|
+
content_tag(:a, link_attributes) do
|
|
23
|
+
content
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def item_attributes
|
|
31
|
+
attrs = {
|
|
32
|
+
role: "link",
|
|
33
|
+
class: cn(PAGE_CLASSES, @class_name),
|
|
34
|
+
"aria-current": "page",
|
|
35
|
+
"aria-disabled": "true"
|
|
36
|
+
}
|
|
37
|
+
attrs.merge!(html_options.except(:href))
|
|
38
|
+
attrs.compact
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def link_attributes
|
|
42
|
+
attrs = {
|
|
43
|
+
href: @href,
|
|
44
|
+
class: cn(LINK_CLASSES, @class_name)
|
|
45
|
+
}
|
|
46
|
+
attrs.merge!(html_options)
|
|
47
|
+
attrs.compact
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Button component with multiple variants and sizes
|
|
5
|
+
# Matches shadcn/ui Button component
|
|
6
|
+
#
|
|
7
|
+
# @example Basic button
|
|
8
|
+
# <%= render Shadcn::ButtonComponent.new do %>
|
|
9
|
+
# Click me
|
|
10
|
+
# <% end %>
|
|
11
|
+
#
|
|
12
|
+
# @example Variant and size
|
|
13
|
+
# <%= render Shadcn::ButtonComponent.new(variant: :destructive, size: :lg) do %>
|
|
14
|
+
# Delete
|
|
15
|
+
# <% end %>
|
|
16
|
+
#
|
|
17
|
+
# @example As link
|
|
18
|
+
# <%= render Shadcn::ButtonComponent.new(href: "/path", variant: :link) do %>
|
|
19
|
+
# Go somewhere
|
|
20
|
+
# <% end %>
|
|
21
|
+
#
|
|
22
|
+
# @example Icon button
|
|
23
|
+
# <%= render Shadcn::ButtonComponent.new(size: :icon, aria: { label: "Settings" }) do %>
|
|
24
|
+
# <%= icon "settings" %>
|
|
25
|
+
# <% end %>
|
|
26
|
+
#
|
|
27
|
+
class ButtonComponent < BaseComponent
|
|
28
|
+
# Available button variants
|
|
29
|
+
VARIANTS = {
|
|
30
|
+
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
|
31
|
+
destructive: "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90",
|
|
32
|
+
outline: "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
|
|
33
|
+
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
|
34
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
35
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
# Available button sizes
|
|
39
|
+
SIZES = {
|
|
40
|
+
default: "h-9 px-4 py-2",
|
|
41
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
42
|
+
lg: "h-10 rounded-md px-8",
|
|
43
|
+
icon: "h-9 w-9",
|
|
44
|
+
icon_sm: "h-8 w-8",
|
|
45
|
+
icon_lg: "h-10 w-10"
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
48
|
+
# Base classes applied to all buttons
|
|
49
|
+
BASE_CLASSES = "inline-flex items-center justify-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0"
|
|
50
|
+
|
|
51
|
+
# @param variant [Symbol] Button style variant (:default, :destructive, :outline, :secondary, :ghost, :link)
|
|
52
|
+
# @param size [Symbol] Button size (:default, :sm, :lg, :icon, :icon_sm, :icon_lg)
|
|
53
|
+
# @param href [String, nil] If provided, renders as an anchor tag
|
|
54
|
+
# @param type [String] Button type attribute (button, submit, reset)
|
|
55
|
+
# @param disabled [Boolean] Whether button is disabled
|
|
56
|
+
# @param loading [Boolean] Whether button shows loading state
|
|
57
|
+
# @param class_name [String, nil] Additional CSS classes
|
|
58
|
+
# @param data [Hash] Data attributes
|
|
59
|
+
# @param html_options [Hash] Additional HTML attributes
|
|
60
|
+
def initialize(
|
|
61
|
+
variant: :default,
|
|
62
|
+
size: :default,
|
|
63
|
+
href: nil,
|
|
64
|
+
type: "button",
|
|
65
|
+
disabled: false,
|
|
66
|
+
loading: false,
|
|
67
|
+
**options
|
|
68
|
+
)
|
|
69
|
+
super(**options)
|
|
70
|
+
@variant = variant.to_sym
|
|
71
|
+
@size = size.to_sym
|
|
72
|
+
@href = href
|
|
73
|
+
@type = type
|
|
74
|
+
@disabled = disabled
|
|
75
|
+
@loading = loading
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def call
|
|
79
|
+
if @href
|
|
80
|
+
link_tag
|
|
81
|
+
else
|
|
82
|
+
button_tag
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def button_tag
|
|
89
|
+
content_tag(:button, button_content, button_attributes)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def link_tag
|
|
93
|
+
content_tag(:a, button_content, link_attributes)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def button_content
|
|
97
|
+
if @loading
|
|
98
|
+
safe_join([loading_spinner, content])
|
|
99
|
+
else
|
|
100
|
+
content
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def loading_spinner
|
|
105
|
+
content_tag(:span, "", class: "animate-spin h-4 w-4 border-2 border-current border-t-transparent rounded-full", "aria-hidden": true)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def button_classes
|
|
109
|
+
cn(
|
|
110
|
+
BASE_CLASSES,
|
|
111
|
+
VARIANTS[@variant],
|
|
112
|
+
SIZES[@size],
|
|
113
|
+
class_name
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def button_attributes
|
|
118
|
+
attrs = html_options.merge(
|
|
119
|
+
type: @type,
|
|
120
|
+
class: button_classes,
|
|
121
|
+
disabled: @disabled || @loading || nil,
|
|
122
|
+
"aria-disabled": (@disabled || @loading) ? "true" : nil,
|
|
123
|
+
"aria-busy": @loading ? "true" : nil
|
|
124
|
+
)
|
|
125
|
+
attrs.merge!(build_data)
|
|
126
|
+
attrs.compact
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def link_attributes
|
|
130
|
+
attrs = html_options.merge(
|
|
131
|
+
href: @href,
|
|
132
|
+
class: button_classes,
|
|
133
|
+
role: "button",
|
|
134
|
+
"aria-disabled": @disabled ? "true" : nil,
|
|
135
|
+
tabindex: @disabled ? "-1" : nil
|
|
136
|
+
)
|
|
137
|
+
attrs.merge!(build_data)
|
|
138
|
+
attrs.compact
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Button Group component for grouping related buttons
|
|
5
|
+
# Matches shadcn/ui Button Group pattern
|
|
6
|
+
#
|
|
7
|
+
# @example Basic button group
|
|
8
|
+
# <%= render Shadcn::ButtonGroupComponent.new do |group| %>
|
|
9
|
+
# <% group.with_button(variant: :outline) { "Left" } %>
|
|
10
|
+
# <% group.with_button(variant: :outline) { "Center" } %>
|
|
11
|
+
# <% group.with_button(variant: :outline) { "Right" } %>
|
|
12
|
+
# <% end %>
|
|
13
|
+
#
|
|
14
|
+
# @example With different variants
|
|
15
|
+
# <%= render Shadcn::ButtonGroupComponent.new do |group| %>
|
|
16
|
+
# <% group.with_button { "Save" } %>
|
|
17
|
+
# <% group.with_button(variant: :outline) { "Cancel" } %>
|
|
18
|
+
# <% end %>
|
|
19
|
+
#
|
|
20
|
+
# @example Vertical orientation
|
|
21
|
+
# <%= render Shadcn::ButtonGroupComponent.new(orientation: :vertical) do |group| %>
|
|
22
|
+
# <% group.with_button(variant: :outline) { "Top" } %>
|
|
23
|
+
# <% group.with_button(variant: :outline) { "Middle" } %>
|
|
24
|
+
# <% group.with_button(variant: :outline) { "Bottom" } %>
|
|
25
|
+
# <% end %>
|
|
26
|
+
#
|
|
27
|
+
class ButtonGroupComponent < BaseComponent
|
|
28
|
+
ORIENTATIONS = {
|
|
29
|
+
horizontal: "flex-row",
|
|
30
|
+
vertical: "flex-col"
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
BASE_CLASSES = "inline-flex"
|
|
34
|
+
|
|
35
|
+
# Button slot - renders Button components with adjusted border radius
|
|
36
|
+
renders_many :buttons, lambda { |**options, &block|
|
|
37
|
+
# Buttons in a group need special border radius handling
|
|
38
|
+
options[:class_name] = cn(
|
|
39
|
+
"rounded-none first:rounded-l-md last:rounded-r-md",
|
|
40
|
+
"-ml-px first:ml-0", # Collapse borders
|
|
41
|
+
options[:class_name]
|
|
42
|
+
)
|
|
43
|
+
Shadcn::ButtonComponent.new(**options, &block)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# @param orientation [Symbol] Layout orientation (:horizontal, :vertical)
|
|
47
|
+
def initialize(orientation: :horizontal, **options)
|
|
48
|
+
super(**options)
|
|
49
|
+
@orientation = orientation.to_sym
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def call
|
|
53
|
+
tag.div(class: group_classes, role: "group", **html_options.merge(build_data)) do
|
|
54
|
+
safe_join(buttons)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def group_classes
|
|
61
|
+
cn(
|
|
62
|
+
BASE_CLASSES,
|
|
63
|
+
ORIENTATIONS[@orientation],
|
|
64
|
+
@orientation == :vertical ? "first:[&>*]:rounded-t-md last:[&>*]:rounded-b-md [&>*]:rounded-none [&>*]:-mt-px [&>*]:first:mt-0" : "",
|
|
65
|
+
class_name
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|