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,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Empty Header component - wraps media, title, and description
|
|
5
|
+
class EmptyHeaderComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "flex flex-col items-center gap-2"
|
|
7
|
+
|
|
8
|
+
# Media slot for icon, image, or avatar
|
|
9
|
+
renders_one :media, lambda { |variant: :default, **options|
|
|
10
|
+
EmptyMediaComponent.new(variant: variant, **options)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
# Title slot
|
|
14
|
+
renders_one :title, lambda { |**options|
|
|
15
|
+
EmptyTitleComponent.new(**options)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# Description slot
|
|
19
|
+
renders_one :description, lambda { |**options|
|
|
20
|
+
EmptyDescriptionComponent.new(**options)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def call
|
|
24
|
+
content_tag(:div, class: merge_classes(BASE_CLASSES), **html_options.merge(build_data)) do
|
|
25
|
+
safe_join([media, title, description, content].compact)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Empty Media component - displays icon, image, or avatar
|
|
5
|
+
class EmptyMediaComponent < BaseComponent
|
|
6
|
+
VARIANTS = {
|
|
7
|
+
default: "",
|
|
8
|
+
icon: "flex size-12 items-center justify-center rounded-full bg-muted [&>svg]:size-6 [&>svg]:text-muted-foreground"
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
# @param variant [Symbol] :default or :icon
|
|
12
|
+
def initialize(variant: :default, **options)
|
|
13
|
+
super(**options)
|
|
14
|
+
@variant = variant.to_sym
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
content_tag(:div, content, class: merge_classes(VARIANTS[@variant]), **html_options.merge(build_data))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Empty Title component
|
|
5
|
+
class EmptyTitleComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "text-lg font-semibold"
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
content_tag(:h3, content, class: merge_classes(BASE_CLASSES), **html_options.merge(build_data))
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Field component for form field wrapper with label, input, description, and error
|
|
5
|
+
# Provides a consistent pattern for form fields
|
|
6
|
+
#
|
|
7
|
+
# @example Basic field
|
|
8
|
+
# <%= render Shadcn::FieldComponent.new do |field| %>
|
|
9
|
+
# <% field.with_label { "Email" } %>
|
|
10
|
+
# <% field.with_input(type: :email, placeholder: "you@example.com") %>
|
|
11
|
+
# <% end %>
|
|
12
|
+
#
|
|
13
|
+
# @example With description
|
|
14
|
+
# <%= render Shadcn::FieldComponent.new do |field| %>
|
|
15
|
+
# <% field.with_label { "Username" } %>
|
|
16
|
+
# <% field.with_input %>
|
|
17
|
+
# <% field.with_description { "This is your public display name." } %>
|
|
18
|
+
# <% end %>
|
|
19
|
+
#
|
|
20
|
+
# @example With error
|
|
21
|
+
# <%= render Shadcn::FieldComponent.new do |field| %>
|
|
22
|
+
# <% field.with_label { "Password" } %>
|
|
23
|
+
# <% field.with_input(type: :password) %>
|
|
24
|
+
# <% field.with_error { "Password is required." } %>
|
|
25
|
+
# <% end %>
|
|
26
|
+
#
|
|
27
|
+
# @example With custom content
|
|
28
|
+
# <%= render Shadcn::FieldComponent.new do |field| %>
|
|
29
|
+
# <% field.with_label { "Bio" } %>
|
|
30
|
+
# <% field.with_control do %>
|
|
31
|
+
# <%= render Shadcn::TextareaComponent.new(placeholder: "Tell us about yourself") %>
|
|
32
|
+
# <% end %>
|
|
33
|
+
# <% end %>
|
|
34
|
+
#
|
|
35
|
+
class FieldComponent < BaseComponent
|
|
36
|
+
BASE_CLASSES = "space-y-2"
|
|
37
|
+
|
|
38
|
+
# Label slot
|
|
39
|
+
renders_one :label, lambda { |required: false, **options, &block|
|
|
40
|
+
options[:for] ||= @input_id
|
|
41
|
+
options[:required] = required
|
|
42
|
+
Shadcn::LabelComponent.new(**options, &block)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Input slot - renders an Input component
|
|
46
|
+
renders_one :input, lambda { |**options|
|
|
47
|
+
options[:id] ||= @input_id
|
|
48
|
+
options[:name] ||= @name
|
|
49
|
+
if @has_error
|
|
50
|
+
options[:class_name] = cn("border-destructive focus-visible:ring-destructive", options[:class_name])
|
|
51
|
+
end
|
|
52
|
+
Shadcn::InputComponent.new(**options)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Control slot - for custom controls (textarea, select, etc.)
|
|
56
|
+
renders_one :control
|
|
57
|
+
|
|
58
|
+
# Description slot
|
|
59
|
+
renders_one :description, "DescriptionComponent"
|
|
60
|
+
|
|
61
|
+
# Error slot
|
|
62
|
+
renders_one :error, "ErrorComponent"
|
|
63
|
+
|
|
64
|
+
# @param name [String, nil] Input name attribute
|
|
65
|
+
# @param id [String, nil] Input ID (auto-generated if not provided)
|
|
66
|
+
def initialize(name: nil, id: nil, **options)
|
|
67
|
+
super(**options)
|
|
68
|
+
@name = name
|
|
69
|
+
@input_id = id || generate_id
|
|
70
|
+
@has_error = false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def before_render
|
|
74
|
+
# Track if error is present for styling
|
|
75
|
+
@has_error = error.present?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def call
|
|
79
|
+
tag.div(class: merge_classes(BASE_CLASSES), **html_options.merge(build_data)) do
|
|
80
|
+
safe_join([
|
|
81
|
+
label,
|
|
82
|
+
control || input,
|
|
83
|
+
description,
|
|
84
|
+
error
|
|
85
|
+
].compact)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def generate_id
|
|
92
|
+
"field-#{SecureRandom.hex(4)}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Description subcomponent
|
|
96
|
+
class DescriptionComponent < BaseComponent
|
|
97
|
+
BASE_CLASSES = "text-[0.8rem] text-muted-foreground"
|
|
98
|
+
|
|
99
|
+
def call
|
|
100
|
+
tag.p(content, class: merge_classes(BASE_CLASSES), **html_options.merge(build_data))
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Error subcomponent
|
|
105
|
+
class ErrorComponent < BaseComponent
|
|
106
|
+
BASE_CLASSES = "text-[0.8rem] font-medium text-destructive"
|
|
107
|
+
|
|
108
|
+
def call
|
|
109
|
+
tag.p(content, class: merge_classes(BASE_CLASSES), role: "alert", **html_options.merge(build_data))
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Hover Card component for displaying rich content on hover
|
|
5
|
+
# Matches shadcn/ui HoverCard component
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage
|
|
8
|
+
# <%= render Shadcn::HoverCardComponent.new do |card| %>
|
|
9
|
+
# <% card.with_trigger do %>
|
|
10
|
+
# <a href="#">@username</a>
|
|
11
|
+
# <% end %>
|
|
12
|
+
# <% card.with_card_content do %>
|
|
13
|
+
# <div>User profile preview</div>
|
|
14
|
+
# <% end %>
|
|
15
|
+
# <% end %>
|
|
16
|
+
#
|
|
17
|
+
class HoverCardComponent < BaseComponent
|
|
18
|
+
renders_one :trigger
|
|
19
|
+
renders_one :card_content, lambda { |**options|
|
|
20
|
+
HoverCardContentComponent.new(**options)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# @param open_delay [Integer] Delay in ms before opening
|
|
24
|
+
# @param close_delay [Integer] Delay in ms before closing
|
|
25
|
+
def initialize(open_delay: 700, close_delay: 300, **options)
|
|
26
|
+
super(**options)
|
|
27
|
+
@open_delay = open_delay
|
|
28
|
+
@close_delay = close_delay
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def call
|
|
32
|
+
content_tag(:div, build_card_content, card_attributes)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def build_card_content
|
|
38
|
+
safe_join([
|
|
39
|
+
trigger_wrapper,
|
|
40
|
+
card_content
|
|
41
|
+
].compact)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def trigger_wrapper
|
|
45
|
+
return unless trigger
|
|
46
|
+
|
|
47
|
+
content_tag(:div, trigger, {
|
|
48
|
+
"data-shadcn--hover-card-target": "trigger"
|
|
49
|
+
})
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def card_attributes
|
|
53
|
+
attrs = {
|
|
54
|
+
class: cn("relative inline-block", class_name),
|
|
55
|
+
"data-controller": "shadcn--hover-card",
|
|
56
|
+
"data-shadcn--hover-card-open-delay-value": @open_delay,
|
|
57
|
+
"data-shadcn--hover-card-close-delay-value": @close_delay
|
|
58
|
+
}
|
|
59
|
+
attrs.merge!(html_options)
|
|
60
|
+
attrs.merge!(build_data)
|
|
61
|
+
attrs.compact
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Hover Card Content component
|
|
5
|
+
class HoverCardContentComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
|
|
7
|
+
|
|
8
|
+
# @param side [Symbol] :top, :right, :bottom, or :left
|
|
9
|
+
# @param align [Symbol] :start, :center, or :end
|
|
10
|
+
def initialize(side: :bottom, align: :center, **options, &block)
|
|
11
|
+
super(**options, &block)
|
|
12
|
+
@side = side
|
|
13
|
+
@align = align
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
content_tag(:div, content, content_attributes)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def content_attributes
|
|
23
|
+
attrs = {
|
|
24
|
+
class: merge_classes(BASE_CLASSES),
|
|
25
|
+
role: "tooltip",
|
|
26
|
+
"data-state": "closed",
|
|
27
|
+
"data-side": @side.to_s,
|
|
28
|
+
"data-align": @align.to_s,
|
|
29
|
+
"data-shadcn--hover-card-target": "content",
|
|
30
|
+
style: "display: none; position: absolute;"
|
|
31
|
+
}
|
|
32
|
+
attrs.merge!(html_options)
|
|
33
|
+
attrs.compact
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Input component for form fields
|
|
5
|
+
# Matches shadcn/ui Input component
|
|
6
|
+
#
|
|
7
|
+
# @example Basic input
|
|
8
|
+
# <%= render Shadcn::InputComponent.new(name: "email", type: "email", placeholder: "Email") %>
|
|
9
|
+
#
|
|
10
|
+
# @example With label
|
|
11
|
+
# <%= render Shadcn::LabelComponent.new(for: "email") { "Email" } %>
|
|
12
|
+
# <%= render Shadcn::InputComponent.new(id: "email", name: "email", type: "email") %>
|
|
13
|
+
#
|
|
14
|
+
# @example Disabled input
|
|
15
|
+
# <%= render Shadcn::InputComponent.new(name: "locked", disabled: true, value: "Can't change") %>
|
|
16
|
+
#
|
|
17
|
+
# @example File input
|
|
18
|
+
# <%= render Shadcn::InputComponent.new(name: "file", type: "file") %>
|
|
19
|
+
#
|
|
20
|
+
class InputComponent < BaseComponent
|
|
21
|
+
BASE_CLASSES = "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
|
|
22
|
+
|
|
23
|
+
# @param type [String] Input type (text, email, password, etc.)
|
|
24
|
+
# @param name [String, nil] Input name attribute
|
|
25
|
+
# @param id [String, nil] Input id attribute
|
|
26
|
+
# @param value [String, nil] Input value
|
|
27
|
+
# @param placeholder [String, nil] Placeholder text
|
|
28
|
+
# @param disabled [Boolean] Whether input is disabled
|
|
29
|
+
# @param required [Boolean] Whether input is required
|
|
30
|
+
# @param readonly [Boolean] Whether input is readonly
|
|
31
|
+
# @param autofocus [Boolean] Whether to autofocus
|
|
32
|
+
# @param autocomplete [String, nil] Autocomplete attribute
|
|
33
|
+
# @param pattern [String, nil] Validation pattern
|
|
34
|
+
# @param min [String, Integer, nil] Minimum value (for number/date inputs)
|
|
35
|
+
# @param max [String, Integer, nil] Maximum value (for number/date inputs)
|
|
36
|
+
# @param step [String, Integer, nil] Step value (for number inputs)
|
|
37
|
+
# @param minlength [Integer, nil] Minimum length
|
|
38
|
+
# @param maxlength [Integer, nil] Maximum length
|
|
39
|
+
def initialize(
|
|
40
|
+
type: "text",
|
|
41
|
+
name: nil,
|
|
42
|
+
id: nil,
|
|
43
|
+
value: nil,
|
|
44
|
+
placeholder: nil,
|
|
45
|
+
disabled: false,
|
|
46
|
+
required: false,
|
|
47
|
+
readonly: false,
|
|
48
|
+
autofocus: false,
|
|
49
|
+
autocomplete: nil,
|
|
50
|
+
pattern: nil,
|
|
51
|
+
min: nil,
|
|
52
|
+
max: nil,
|
|
53
|
+
step: nil,
|
|
54
|
+
minlength: nil,
|
|
55
|
+
maxlength: nil,
|
|
56
|
+
**options
|
|
57
|
+
)
|
|
58
|
+
super(**options)
|
|
59
|
+
@type = type
|
|
60
|
+
@name = name
|
|
61
|
+
@id = id
|
|
62
|
+
@value = value
|
|
63
|
+
@placeholder = placeholder
|
|
64
|
+
@disabled = disabled
|
|
65
|
+
@required = required
|
|
66
|
+
@readonly = readonly
|
|
67
|
+
@autofocus = autofocus
|
|
68
|
+
@autocomplete = autocomplete
|
|
69
|
+
@pattern = pattern
|
|
70
|
+
@min = min
|
|
71
|
+
@max = max
|
|
72
|
+
@step = step
|
|
73
|
+
@minlength = minlength
|
|
74
|
+
@maxlength = maxlength
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def call
|
|
78
|
+
tag(:input, input_attributes)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def input_attributes
|
|
84
|
+
attrs = {
|
|
85
|
+
type: @type,
|
|
86
|
+
name: @name,
|
|
87
|
+
id: @id,
|
|
88
|
+
value: @value,
|
|
89
|
+
placeholder: @placeholder,
|
|
90
|
+
disabled: @disabled || nil,
|
|
91
|
+
required: @required || nil,
|
|
92
|
+
readonly: @readonly || nil,
|
|
93
|
+
autofocus: @autofocus || nil,
|
|
94
|
+
autocomplete: @autocomplete,
|
|
95
|
+
pattern: @pattern,
|
|
96
|
+
min: @min,
|
|
97
|
+
max: @max,
|
|
98
|
+
step: @step,
|
|
99
|
+
minlength: @minlength,
|
|
100
|
+
maxlength: @maxlength,
|
|
101
|
+
class: merge_classes(BASE_CLASSES)
|
|
102
|
+
}
|
|
103
|
+
attrs.merge!(html_options)
|
|
104
|
+
attrs.merge!(build_data)
|
|
105
|
+
attrs.compact
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Input Group component for inputs with prefix/suffix addons
|
|
5
|
+
# Matches shadcn/ui Input Group pattern
|
|
6
|
+
#
|
|
7
|
+
# @example With prefix icon
|
|
8
|
+
# <%= render Shadcn::InputGroupComponent.new do |group| %>
|
|
9
|
+
# <% group.with_prefix do %>
|
|
10
|
+
# <svg><!-- search icon --></svg>
|
|
11
|
+
# <% end %>
|
|
12
|
+
# <% group.with_input(placeholder: "Search...") %>
|
|
13
|
+
# <% end %>
|
|
14
|
+
#
|
|
15
|
+
# @example With suffix icon
|
|
16
|
+
# <%= render Shadcn::InputGroupComponent.new do |group| %>
|
|
17
|
+
# <% group.with_input(type: :email, placeholder: "Email") %>
|
|
18
|
+
# <% group.with_suffix do %>
|
|
19
|
+
# <svg><!-- mail icon --></svg>
|
|
20
|
+
# <% end %>
|
|
21
|
+
# <% end %>
|
|
22
|
+
#
|
|
23
|
+
# @example With prefix text
|
|
24
|
+
# <%= render Shadcn::InputGroupComponent.new do |group| %>
|
|
25
|
+
# <% group.with_prefix { "https://" } %>
|
|
26
|
+
# <% group.with_input(placeholder: "example.com") %>
|
|
27
|
+
# <% end %>
|
|
28
|
+
#
|
|
29
|
+
# @example With both prefix and suffix
|
|
30
|
+
# <%= render Shadcn::InputGroupComponent.new do |group| %>
|
|
31
|
+
# <% group.with_prefix { "$" } %>
|
|
32
|
+
# <% group.with_input(type: :number, placeholder: "0.00") %>
|
|
33
|
+
# <% group.with_suffix { "USD" } %>
|
|
34
|
+
# <% end %>
|
|
35
|
+
#
|
|
36
|
+
class InputGroupComponent < BaseComponent
|
|
37
|
+
BASE_CLASSES = "flex items-center rounded-md border border-input ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2"
|
|
38
|
+
|
|
39
|
+
# Prefix addon slot
|
|
40
|
+
renders_one :prefix, "AddonComponent"
|
|
41
|
+
|
|
42
|
+
# Input slot
|
|
43
|
+
renders_one :input, lambda { |**options|
|
|
44
|
+
# Remove border, ring, rounded corners and shadow from input since the group handles it
|
|
45
|
+
options[:class_name] = cn(
|
|
46
|
+
"border-0 rounded-none shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 focus:ring-0 focus:outline-none",
|
|
47
|
+
options[:class_name]
|
|
48
|
+
)
|
|
49
|
+
Shadcn::InputComponent.new(**options)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Suffix addon slot
|
|
53
|
+
renders_one :suffix, "AddonComponent"
|
|
54
|
+
|
|
55
|
+
def call
|
|
56
|
+
tag.div(class: merge_classes(BASE_CLASSES), **html_options.merge(build_data)) do
|
|
57
|
+
safe_join([prefix, input, suffix].compact)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Addon subcomponent for prefix/suffix
|
|
62
|
+
class AddonComponent < BaseComponent
|
|
63
|
+
BASE_CLASSES = "flex items-center justify-center px-3 text-sm text-muted-foreground"
|
|
64
|
+
|
|
65
|
+
def call
|
|
66
|
+
tag.span(content, class: merge_classes(BASE_CLASSES), **html_options.merge(build_data))
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Input OTP component for one-time password entry
|
|
5
|
+
# Matches shadcn/ui Input OTP component
|
|
6
|
+
#
|
|
7
|
+
# @example Basic 6-digit OTP
|
|
8
|
+
# <%= render Shadcn::InputOtpComponent.new(length: 6, name: "otp") %>
|
|
9
|
+
#
|
|
10
|
+
# @example With groups (3 + 3)
|
|
11
|
+
# <%= render Shadcn::InputOtpComponent.new(length: 6, name: "otp") do |otp| %>
|
|
12
|
+
# <% otp.with_group(slots: 3) %>
|
|
13
|
+
# <% otp.with_separator %>
|
|
14
|
+
# <% otp.with_group(slots: 3) %>
|
|
15
|
+
# <% end %>
|
|
16
|
+
#
|
|
17
|
+
# @example 4-digit PIN
|
|
18
|
+
# <%= render Shadcn::InputOtpComponent.new(length: 4, name: "pin", pattern: "^[0-9]*$") %>
|
|
19
|
+
#
|
|
20
|
+
# @example Disabled
|
|
21
|
+
# <%= render Shadcn::InputOtpComponent.new(length: 6, name: "otp", disabled: true) %>
|
|
22
|
+
#
|
|
23
|
+
class InputOtpComponent < BaseComponent
|
|
24
|
+
BASE_CLASSES = "flex items-center gap-2"
|
|
25
|
+
SLOT_CLASSES = "relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md"
|
|
26
|
+
SLOT_ACTIVE_CLASSES = "z-10 ring-1 ring-ring"
|
|
27
|
+
CARET_CLASSES = "pointer-events-none absolute inset-0 flex items-center justify-center"
|
|
28
|
+
CARET_BLINK_CLASSES = "animate-caret-blink h-4 w-px bg-foreground duration-1000"
|
|
29
|
+
|
|
30
|
+
# Groups for visual grouping of slots
|
|
31
|
+
renders_many :groups, "GroupComponent"
|
|
32
|
+
|
|
33
|
+
# Separators between groups
|
|
34
|
+
renders_many :separators, "SeparatorComponent"
|
|
35
|
+
|
|
36
|
+
# @param length [Integer] Number of OTP digits
|
|
37
|
+
# @param name [String] Input name for form submission
|
|
38
|
+
# @param pattern [String] Regex pattern for validation (defaults to alphanumeric)
|
|
39
|
+
# @param disabled [Boolean] Whether the input is disabled
|
|
40
|
+
# @param required [Boolean] Whether the input is required
|
|
41
|
+
# @param autocomplete [String] Autocomplete attribute (defaults to "one-time-code")
|
|
42
|
+
def initialize(length: 6, name: nil, pattern: nil, disabled: false, required: false, autocomplete: "one-time-code", **options)
|
|
43
|
+
super(**options)
|
|
44
|
+
@length = length
|
|
45
|
+
@name = name
|
|
46
|
+
@pattern = pattern
|
|
47
|
+
@disabled = disabled
|
|
48
|
+
@required = required
|
|
49
|
+
@autocomplete = autocomplete
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def call
|
|
53
|
+
tag.div(**container_attributes) do
|
|
54
|
+
safe_join([
|
|
55
|
+
hidden_input,
|
|
56
|
+
groups.any? ? render_with_groups : render_default_slots
|
|
57
|
+
])
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def container_attributes
|
|
64
|
+
{
|
|
65
|
+
class: merge_classes(BASE_CLASSES),
|
|
66
|
+
data: {
|
|
67
|
+
controller: "shadcn--input-otp",
|
|
68
|
+
"shadcn--input-otp-length-value": @length,
|
|
69
|
+
"shadcn--input-otp-pattern-value": @pattern,
|
|
70
|
+
"shadcn--input-otp-disabled-value": @disabled
|
|
71
|
+
}
|
|
72
|
+
}.merge(html_options).merge(build_data).compact
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def hidden_input
|
|
76
|
+
tag.input(
|
|
77
|
+
type: "hidden",
|
|
78
|
+
name: @name,
|
|
79
|
+
data: { "shadcn--input-otp-target": "hiddenInput" }
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def render_with_groups
|
|
84
|
+
parts = []
|
|
85
|
+
slot_index = 0
|
|
86
|
+
|
|
87
|
+
groups.each_with_index do |group, group_index|
|
|
88
|
+
# Render the group with its slots
|
|
89
|
+
group_slots = group.slots.times.map do |_|
|
|
90
|
+
slot = render_slot(slot_index)
|
|
91
|
+
slot_index += 1
|
|
92
|
+
slot
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
parts << tag.div(class: "flex items-center") { safe_join(group_slots) }
|
|
96
|
+
|
|
97
|
+
# Add separator after group if there's another group
|
|
98
|
+
if group_index < groups.size - 1
|
|
99
|
+
separator = separators[group_index]
|
|
100
|
+
parts << (separator || default_separator)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
safe_join(parts)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def default_separator
|
|
108
|
+
tag.div(class: "flex items-center justify-center px-2", role: "separator") do
|
|
109
|
+
tag.span(class: "text-muted-foreground") { "-" }
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def render_default_slots
|
|
114
|
+
# Render all slots in one group
|
|
115
|
+
slots = @length.times.map do |index|
|
|
116
|
+
render_slot(index)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
tag.div(class: "flex items-center") { safe_join(slots) }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def render_slot(index)
|
|
123
|
+
tag.div(
|
|
124
|
+
class: SLOT_CLASSES,
|
|
125
|
+
data: {
|
|
126
|
+
"shadcn--input-otp-target": "slot",
|
|
127
|
+
index: index,
|
|
128
|
+
action: "click->shadcn--input-otp#focusSlot"
|
|
129
|
+
}
|
|
130
|
+
) do
|
|
131
|
+
safe_join([
|
|
132
|
+
tag.input(
|
|
133
|
+
type: "text",
|
|
134
|
+
maxlength: 1,
|
|
135
|
+
inputmode: "numeric",
|
|
136
|
+
autocomplete: index == 0 ? @autocomplete : "off",
|
|
137
|
+
disabled: @disabled || nil,
|
|
138
|
+
required: @required && index == 0 || nil,
|
|
139
|
+
class: "absolute inset-0 w-full h-full text-center bg-transparent outline-none border-0 focus:ring-0",
|
|
140
|
+
data: {
|
|
141
|
+
"shadcn--input-otp-target": "input",
|
|
142
|
+
index: index,
|
|
143
|
+
action: "input->shadcn--input-otp#handleInput keydown->shadcn--input-otp#handleKeydown focus->shadcn--input-otp#handleFocus blur->shadcn--input-otp#handleBlur paste->shadcn--input-otp#handlePaste"
|
|
144
|
+
}
|
|
145
|
+
),
|
|
146
|
+
tag.div(class: CARET_CLASSES, data: { "shadcn--input-otp-target": "caret" }) do
|
|
147
|
+
tag.div(class: CARET_BLINK_CLASSES)
|
|
148
|
+
end
|
|
149
|
+
])
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Group subcomponent
|
|
154
|
+
class GroupComponent < BaseComponent
|
|
155
|
+
BASE_CLASSES = "flex items-center"
|
|
156
|
+
|
|
157
|
+
# @param slots [Integer] Number of slots in this group
|
|
158
|
+
def initialize(slots: 3, **options)
|
|
159
|
+
super(**options)
|
|
160
|
+
@slots = slots
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
attr_reader :slots
|
|
164
|
+
|
|
165
|
+
def call
|
|
166
|
+
tag.div(class: merge_classes(BASE_CLASSES), **html_options.merge(build_data)) do
|
|
167
|
+
content
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Separator subcomponent
|
|
173
|
+
class SeparatorComponent < BaseComponent
|
|
174
|
+
BASE_CLASSES = "flex items-center justify-center px-2"
|
|
175
|
+
|
|
176
|
+
def call
|
|
177
|
+
tag.div(class: merge_classes(BASE_CLASSES), role: "separator", **html_options.merge(build_data)) do
|
|
178
|
+
content.presence || tag.span(class: "text-muted-foreground") { "-" }
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Item Actions component - container for action buttons
|
|
5
|
+
class ItemActionsComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "shrink-0 flex items-center gap-2"
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
content_tag(:div, content, class: merge_classes(BASE_CLASSES), **html_options.merge(build_data))
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|