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,341 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Rails
|
|
5
|
+
class Configuration
|
|
6
|
+
# Style preset: currently only "default" is supported
|
|
7
|
+
attr_accessor :style
|
|
8
|
+
|
|
9
|
+
# Base color theme: neutral, stone, zinc, gray, slate
|
|
10
|
+
attr_accessor :base_color
|
|
11
|
+
|
|
12
|
+
# Whether to use CSS variables for theming
|
|
13
|
+
attr_accessor :css_variables
|
|
14
|
+
|
|
15
|
+
# Prefix for Tailwind classes (e.g., "tw-")
|
|
16
|
+
attr_accessor :tailwind_prefix
|
|
17
|
+
|
|
18
|
+
# Default radius for components
|
|
19
|
+
attr_accessor :radius
|
|
20
|
+
|
|
21
|
+
# Dark mode strategy: :class, :media, or :selector
|
|
22
|
+
attr_accessor :dark_mode
|
|
23
|
+
|
|
24
|
+
# Icon library to use: :lucide (default), :heroicons, etc.
|
|
25
|
+
attr_accessor :icon_library
|
|
26
|
+
|
|
27
|
+
# Component aliases (for overriding default component classes)
|
|
28
|
+
attr_reader :component_aliases
|
|
29
|
+
|
|
30
|
+
# Additional themes (name => css_variables hash)
|
|
31
|
+
attr_reader :themes
|
|
32
|
+
|
|
33
|
+
def initialize
|
|
34
|
+
@style = "default"
|
|
35
|
+
@base_color = "neutral"
|
|
36
|
+
@css_variables = true
|
|
37
|
+
@tailwind_prefix = ""
|
|
38
|
+
@radius = "0.5rem"
|
|
39
|
+
@dark_mode = :class
|
|
40
|
+
@icon_library = :lucide
|
|
41
|
+
@component_aliases = {}
|
|
42
|
+
@themes = {}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Register a custom component alias
|
|
46
|
+
def alias_component(name, klass)
|
|
47
|
+
@component_aliases[name.to_sym] = klass
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Register a custom theme
|
|
51
|
+
def register_theme(name, variables)
|
|
52
|
+
@themes[name.to_sym] = variables
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Get all CSS variables for a theme
|
|
56
|
+
def css_variables_for_theme(theme = :default)
|
|
57
|
+
base_variables.merge(@themes[theme] || {})
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Default CSS variables matching shadcn/ui
|
|
61
|
+
def base_variables
|
|
62
|
+
THEME_VARIABLES[@base_color.to_sym] || THEME_VARIABLES[:neutral]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Available base colors with their CSS variable values
|
|
66
|
+
THEME_VARIABLES = {
|
|
67
|
+
neutral: {
|
|
68
|
+
# Light mode
|
|
69
|
+
background: "0 0% 100%",
|
|
70
|
+
foreground: "0 0% 3.9%",
|
|
71
|
+
card: "0 0% 100%",
|
|
72
|
+
card_foreground: "0 0% 3.9%",
|
|
73
|
+
popover: "0 0% 100%",
|
|
74
|
+
popover_foreground: "0 0% 3.9%",
|
|
75
|
+
primary: "0 0% 9%",
|
|
76
|
+
primary_foreground: "0 0% 98%",
|
|
77
|
+
secondary: "0 0% 96.1%",
|
|
78
|
+
secondary_foreground: "0 0% 9%",
|
|
79
|
+
muted: "0 0% 96.1%",
|
|
80
|
+
muted_foreground: "0 0% 45.1%",
|
|
81
|
+
accent: "0 0% 96.1%",
|
|
82
|
+
accent_foreground: "0 0% 9%",
|
|
83
|
+
destructive: "0 84.2% 60.2%",
|
|
84
|
+
destructive_foreground: "0 0% 98%",
|
|
85
|
+
border: "0 0% 89.8%",
|
|
86
|
+
input: "0 0% 89.8%",
|
|
87
|
+
ring: "0 0% 3.9%",
|
|
88
|
+
radius: "0.5rem",
|
|
89
|
+
# Chart colors
|
|
90
|
+
chart_1: "12 76% 61%",
|
|
91
|
+
chart_2: "173 58% 39%",
|
|
92
|
+
chart_3: "197 37% 24%",
|
|
93
|
+
chart_4: "43 74% 66%",
|
|
94
|
+
chart_5: "27 87% 67%"
|
|
95
|
+
},
|
|
96
|
+
slate: {
|
|
97
|
+
background: "0 0% 100%",
|
|
98
|
+
foreground: "222.2 84% 4.9%",
|
|
99
|
+
card: "0 0% 100%",
|
|
100
|
+
card_foreground: "222.2 84% 4.9%",
|
|
101
|
+
popover: "0 0% 100%",
|
|
102
|
+
popover_foreground: "222.2 84% 4.9%",
|
|
103
|
+
primary: "222.2 47.4% 11.2%",
|
|
104
|
+
primary_foreground: "210 40% 98%",
|
|
105
|
+
secondary: "210 40% 96.1%",
|
|
106
|
+
secondary_foreground: "222.2 47.4% 11.2%",
|
|
107
|
+
muted: "210 40% 96.1%",
|
|
108
|
+
muted_foreground: "215.4 16.3% 46.9%",
|
|
109
|
+
accent: "210 40% 96.1%",
|
|
110
|
+
accent_foreground: "222.2 47.4% 11.2%",
|
|
111
|
+
destructive: "0 84.2% 60.2%",
|
|
112
|
+
destructive_foreground: "210 40% 98%",
|
|
113
|
+
border: "214.3 31.8% 91.4%",
|
|
114
|
+
input: "214.3 31.8% 91.4%",
|
|
115
|
+
ring: "222.2 84% 4.9%",
|
|
116
|
+
radius: "0.5rem",
|
|
117
|
+
chart_1: "12 76% 61%",
|
|
118
|
+
chart_2: "173 58% 39%",
|
|
119
|
+
chart_3: "197 37% 24%",
|
|
120
|
+
chart_4: "43 74% 66%",
|
|
121
|
+
chart_5: "27 87% 67%"
|
|
122
|
+
},
|
|
123
|
+
stone: {
|
|
124
|
+
background: "0 0% 100%",
|
|
125
|
+
foreground: "20 14.3% 4.1%",
|
|
126
|
+
card: "0 0% 100%",
|
|
127
|
+
card_foreground: "20 14.3% 4.1%",
|
|
128
|
+
popover: "0 0% 100%",
|
|
129
|
+
popover_foreground: "20 14.3% 4.1%",
|
|
130
|
+
primary: "24 9.8% 10%",
|
|
131
|
+
primary_foreground: "60 9.1% 97.8%",
|
|
132
|
+
secondary: "60 4.8% 95.9%",
|
|
133
|
+
secondary_foreground: "24 9.8% 10%",
|
|
134
|
+
muted: "60 4.8% 95.9%",
|
|
135
|
+
muted_foreground: "25 5.3% 44.7%",
|
|
136
|
+
accent: "60 4.8% 95.9%",
|
|
137
|
+
accent_foreground: "24 9.8% 10%",
|
|
138
|
+
destructive: "0 84.2% 60.2%",
|
|
139
|
+
destructive_foreground: "60 9.1% 97.8%",
|
|
140
|
+
border: "20 5.9% 90%",
|
|
141
|
+
input: "20 5.9% 90%",
|
|
142
|
+
ring: "20 14.3% 4.1%",
|
|
143
|
+
radius: "0.5rem",
|
|
144
|
+
chart_1: "12 76% 61%",
|
|
145
|
+
chart_2: "173 58% 39%",
|
|
146
|
+
chart_3: "197 37% 24%",
|
|
147
|
+
chart_4: "43 74% 66%",
|
|
148
|
+
chart_5: "27 87% 67%"
|
|
149
|
+
},
|
|
150
|
+
gray: {
|
|
151
|
+
background: "0 0% 100%",
|
|
152
|
+
foreground: "224 71.4% 4.1%",
|
|
153
|
+
card: "0 0% 100%",
|
|
154
|
+
card_foreground: "224 71.4% 4.1%",
|
|
155
|
+
popover: "0 0% 100%",
|
|
156
|
+
popover_foreground: "224 71.4% 4.1%",
|
|
157
|
+
primary: "220.9 39.3% 11%",
|
|
158
|
+
primary_foreground: "210 20% 98%",
|
|
159
|
+
secondary: "220 14.3% 95.9%",
|
|
160
|
+
secondary_foreground: "220.9 39.3% 11%",
|
|
161
|
+
muted: "220 14.3% 95.9%",
|
|
162
|
+
muted_foreground: "220 8.9% 46.1%",
|
|
163
|
+
accent: "220 14.3% 95.9%",
|
|
164
|
+
accent_foreground: "220.9 39.3% 11%",
|
|
165
|
+
destructive: "0 84.2% 60.2%",
|
|
166
|
+
destructive_foreground: "210 20% 98%",
|
|
167
|
+
border: "220 13% 91%",
|
|
168
|
+
input: "220 13% 91%",
|
|
169
|
+
ring: "224 71.4% 4.1%",
|
|
170
|
+
radius: "0.5rem",
|
|
171
|
+
chart_1: "12 76% 61%",
|
|
172
|
+
chart_2: "173 58% 39%",
|
|
173
|
+
chart_3: "197 37% 24%",
|
|
174
|
+
chart_4: "43 74% 66%",
|
|
175
|
+
chart_5: "27 87% 67%"
|
|
176
|
+
},
|
|
177
|
+
zinc: {
|
|
178
|
+
background: "0 0% 100%",
|
|
179
|
+
foreground: "240 10% 3.9%",
|
|
180
|
+
card: "0 0% 100%",
|
|
181
|
+
card_foreground: "240 10% 3.9%",
|
|
182
|
+
popover: "0 0% 100%",
|
|
183
|
+
popover_foreground: "240 10% 3.9%",
|
|
184
|
+
primary: "240 5.9% 10%",
|
|
185
|
+
primary_foreground: "0 0% 98%",
|
|
186
|
+
secondary: "240 4.8% 95.9%",
|
|
187
|
+
secondary_foreground: "240 5.9% 10%",
|
|
188
|
+
muted: "240 4.8% 95.9%",
|
|
189
|
+
muted_foreground: "240 3.8% 46.1%",
|
|
190
|
+
accent: "240 4.8% 95.9%",
|
|
191
|
+
accent_foreground: "240 5.9% 10%",
|
|
192
|
+
destructive: "0 84.2% 60.2%",
|
|
193
|
+
destructive_foreground: "0 0% 98%",
|
|
194
|
+
border: "240 5.9% 90%",
|
|
195
|
+
input: "240 5.9% 90%",
|
|
196
|
+
ring: "240 5.9% 10%",
|
|
197
|
+
radius: "0.5rem",
|
|
198
|
+
chart_1: "12 76% 61%",
|
|
199
|
+
chart_2: "173 58% 39%",
|
|
200
|
+
chart_3: "197 37% 24%",
|
|
201
|
+
chart_4: "43 74% 66%",
|
|
202
|
+
chart_5: "27 87% 67%"
|
|
203
|
+
}
|
|
204
|
+
}.freeze
|
|
205
|
+
|
|
206
|
+
# Dark mode variants for each theme
|
|
207
|
+
DARK_THEME_VARIABLES = {
|
|
208
|
+
neutral: {
|
|
209
|
+
background: "0 0% 3.9%",
|
|
210
|
+
foreground: "0 0% 98%",
|
|
211
|
+
card: "0 0% 3.9%",
|
|
212
|
+
card_foreground: "0 0% 98%",
|
|
213
|
+
popover: "0 0% 3.9%",
|
|
214
|
+
popover_foreground: "0 0% 98%",
|
|
215
|
+
primary: "0 0% 98%",
|
|
216
|
+
primary_foreground: "0 0% 9%",
|
|
217
|
+
secondary: "0 0% 14.9%",
|
|
218
|
+
secondary_foreground: "0 0% 98%",
|
|
219
|
+
muted: "0 0% 14.9%",
|
|
220
|
+
muted_foreground: "0 0% 63.9%",
|
|
221
|
+
accent: "0 0% 14.9%",
|
|
222
|
+
accent_foreground: "0 0% 98%",
|
|
223
|
+
destructive: "0 62.8% 30.6%",
|
|
224
|
+
destructive_foreground: "0 0% 98%",
|
|
225
|
+
border: "0 0% 14.9%",
|
|
226
|
+
input: "0 0% 14.9%",
|
|
227
|
+
ring: "0 0% 83.1%",
|
|
228
|
+
chart_1: "220 70% 50%",
|
|
229
|
+
chart_2: "160 60% 45%",
|
|
230
|
+
chart_3: "30 80% 55%",
|
|
231
|
+
chart_4: "280 65% 60%",
|
|
232
|
+
chart_5: "340 75% 55%"
|
|
233
|
+
},
|
|
234
|
+
slate: {
|
|
235
|
+
background: "222.2 84% 4.9%",
|
|
236
|
+
foreground: "210 40% 98%",
|
|
237
|
+
card: "222.2 84% 4.9%",
|
|
238
|
+
card_foreground: "210 40% 98%",
|
|
239
|
+
popover: "222.2 84% 4.9%",
|
|
240
|
+
popover_foreground: "210 40% 98%",
|
|
241
|
+
primary: "210 40% 98%",
|
|
242
|
+
primary_foreground: "222.2 47.4% 11.2%",
|
|
243
|
+
secondary: "217.2 32.6% 17.5%",
|
|
244
|
+
secondary_foreground: "210 40% 98%",
|
|
245
|
+
muted: "217.2 32.6% 17.5%",
|
|
246
|
+
muted_foreground: "215 20.2% 65.1%",
|
|
247
|
+
accent: "217.2 32.6% 17.5%",
|
|
248
|
+
accent_foreground: "210 40% 98%",
|
|
249
|
+
destructive: "0 62.8% 30.6%",
|
|
250
|
+
destructive_foreground: "210 40% 98%",
|
|
251
|
+
border: "217.2 32.6% 17.5%",
|
|
252
|
+
input: "217.2 32.6% 17.5%",
|
|
253
|
+
ring: "212.7 26.8% 83.9%",
|
|
254
|
+
chart_1: "220 70% 50%",
|
|
255
|
+
chart_2: "160 60% 45%",
|
|
256
|
+
chart_3: "30 80% 55%",
|
|
257
|
+
chart_4: "280 65% 60%",
|
|
258
|
+
chart_5: "340 75% 55%"
|
|
259
|
+
},
|
|
260
|
+
stone: {
|
|
261
|
+
background: "20 14.3% 4.1%",
|
|
262
|
+
foreground: "60 9.1% 97.8%",
|
|
263
|
+
card: "20 14.3% 4.1%",
|
|
264
|
+
card_foreground: "60 9.1% 97.8%",
|
|
265
|
+
popover: "20 14.3% 4.1%",
|
|
266
|
+
popover_foreground: "60 9.1% 97.8%",
|
|
267
|
+
primary: "60 9.1% 97.8%",
|
|
268
|
+
primary_foreground: "24 9.8% 10%",
|
|
269
|
+
secondary: "12 6.5% 15.1%",
|
|
270
|
+
secondary_foreground: "60 9.1% 97.8%",
|
|
271
|
+
muted: "12 6.5% 15.1%",
|
|
272
|
+
muted_foreground: "24 5.4% 63.9%",
|
|
273
|
+
accent: "12 6.5% 15.1%",
|
|
274
|
+
accent_foreground: "60 9.1% 97.8%",
|
|
275
|
+
destructive: "0 62.8% 30.6%",
|
|
276
|
+
destructive_foreground: "60 9.1% 97.8%",
|
|
277
|
+
border: "12 6.5% 15.1%",
|
|
278
|
+
input: "12 6.5% 15.1%",
|
|
279
|
+
ring: "24 5.7% 82.9%",
|
|
280
|
+
chart_1: "220 70% 50%",
|
|
281
|
+
chart_2: "160 60% 45%",
|
|
282
|
+
chart_3: "30 80% 55%",
|
|
283
|
+
chart_4: "280 65% 60%",
|
|
284
|
+
chart_5: "340 75% 55%"
|
|
285
|
+
},
|
|
286
|
+
gray: {
|
|
287
|
+
background: "224 71.4% 4.1%",
|
|
288
|
+
foreground: "210 20% 98%",
|
|
289
|
+
card: "224 71.4% 4.1%",
|
|
290
|
+
card_foreground: "210 20% 98%",
|
|
291
|
+
popover: "224 71.4% 4.1%",
|
|
292
|
+
popover_foreground: "210 20% 98%",
|
|
293
|
+
primary: "210 20% 98%",
|
|
294
|
+
primary_foreground: "220.9 39.3% 11%",
|
|
295
|
+
secondary: "215 27.9% 16.9%",
|
|
296
|
+
secondary_foreground: "210 20% 98%",
|
|
297
|
+
muted: "215 27.9% 16.9%",
|
|
298
|
+
muted_foreground: "217.9 10.6% 64.9%",
|
|
299
|
+
accent: "215 27.9% 16.9%",
|
|
300
|
+
accent_foreground: "210 20% 98%",
|
|
301
|
+
destructive: "0 62.8% 30.6%",
|
|
302
|
+
destructive_foreground: "210 20% 98%",
|
|
303
|
+
border: "215 27.9% 16.9%",
|
|
304
|
+
input: "215 27.9% 16.9%",
|
|
305
|
+
ring: "216 12.2% 83.9%",
|
|
306
|
+
chart_1: "220 70% 50%",
|
|
307
|
+
chart_2: "160 60% 45%",
|
|
308
|
+
chart_3: "30 80% 55%",
|
|
309
|
+
chart_4: "280 65% 60%",
|
|
310
|
+
chart_5: "340 75% 55%"
|
|
311
|
+
},
|
|
312
|
+
zinc: {
|
|
313
|
+
background: "240 10% 3.9%",
|
|
314
|
+
foreground: "0 0% 98%",
|
|
315
|
+
card: "240 10% 3.9%",
|
|
316
|
+
card_foreground: "0 0% 98%",
|
|
317
|
+
popover: "240 10% 3.9%",
|
|
318
|
+
popover_foreground: "0 0% 98%",
|
|
319
|
+
primary: "0 0% 98%",
|
|
320
|
+
primary_foreground: "240 5.9% 10%",
|
|
321
|
+
secondary: "240 3.7% 15.9%",
|
|
322
|
+
secondary_foreground: "0 0% 98%",
|
|
323
|
+
muted: "240 3.7% 15.9%",
|
|
324
|
+
muted_foreground: "240 5% 64.9%",
|
|
325
|
+
accent: "240 3.7% 15.9%",
|
|
326
|
+
accent_foreground: "0 0% 98%",
|
|
327
|
+
destructive: "0 62.8% 30.6%",
|
|
328
|
+
destructive_foreground: "0 0% 98%",
|
|
329
|
+
border: "240 3.7% 15.9%",
|
|
330
|
+
input: "240 3.7% 15.9%",
|
|
331
|
+
ring: "240 4.9% 83.9%",
|
|
332
|
+
chart_1: "220 70% 50%",
|
|
333
|
+
chart_2: "160 60% 45%",
|
|
334
|
+
chart_3: "30 80% 55%",
|
|
335
|
+
chart_4: "280 65% 60%",
|
|
336
|
+
chart_5: "340 75% 55%"
|
|
337
|
+
}
|
|
338
|
+
}.freeze
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Rails
|
|
5
|
+
class Engine < ::Rails::Engine
|
|
6
|
+
isolate_namespace Shadcn::Rails
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Configure autoloading for components
|
|
10
|
+
initializer "shadcn-rails.autoloading", before: :set_autoload_paths do |app|
|
|
11
|
+
components_path = root.join("app/components")
|
|
12
|
+
app.config.autoload_paths << components_path
|
|
13
|
+
|
|
14
|
+
# Enable reloading of engine components in development
|
|
15
|
+
if ::Rails.env.development?
|
|
16
|
+
app.reloaders << app.config.file_watcher.new([], { components_path.to_s => ["rb"] }) do
|
|
17
|
+
# Trigger reload when component files change
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
initializer "shadcn-rails.view_component" do
|
|
23
|
+
ActiveSupport.on_load(:view_component) do
|
|
24
|
+
# ViewComponent is loaded
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
initializer "shadcn-rails.helpers" do
|
|
29
|
+
ActiveSupport.on_load(:action_view) do
|
|
30
|
+
include Shadcn::Rails::Helpers::ClassNameHelper
|
|
31
|
+
include Shadcn::Rails::Helpers::ComponentHelper
|
|
32
|
+
include Shadcn::Rails::Helpers::PaginationHelper
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
initializer "shadcn-rails.assets" do |app|
|
|
37
|
+
if app.config.respond_to?(:assets) && app.config.assets.respond_to?(:paths)
|
|
38
|
+
app.config.assets.paths << root.join("app/assets/stylesheets")
|
|
39
|
+
app.config.assets.paths << root.join("app/assets/javascripts")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
initializer "shadcn-rails.importmap", before: "importmap" do |app|
|
|
44
|
+
if defined?(Importmap)
|
|
45
|
+
app.config.importmap.paths << root.join("config/importmap.rb")
|
|
46
|
+
app.config.importmap.cache_sweepers << root.join("app/assets/javascripts")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Configure view component preview paths after initialization
|
|
51
|
+
initializer "shadcn-rails.preview_paths", after: :load_config_initializers do |app|
|
|
52
|
+
if defined?(ViewComponent::Base) && app.config.respond_to?(:view_component)
|
|
53
|
+
app.config.view_component.preview_paths ||= []
|
|
54
|
+
app.config.view_component.preview_paths << root.join("test/components/previews")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Rails
|
|
5
|
+
module Helpers
|
|
6
|
+
# Helper module providing the cn() function for merging Tailwind CSS classes
|
|
7
|
+
# This is the Ruby equivalent of shadcn/ui's cn() utility
|
|
8
|
+
module ClassNameHelper
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
# Merge CSS classes intelligently, handling Tailwind class conflicts
|
|
12
|
+
# Similar to clsx + tailwind-merge in JavaScript
|
|
13
|
+
#
|
|
14
|
+
# @param args [Array<String, Hash, Array, nil>] Classes to merge
|
|
15
|
+
# @return [String] Merged class string
|
|
16
|
+
#
|
|
17
|
+
# @example Basic usage
|
|
18
|
+
# cn("px-4 py-2", "bg-blue-500") # => "px-4 py-2 bg-blue-500"
|
|
19
|
+
#
|
|
20
|
+
# @example With conditionals
|
|
21
|
+
# cn("base-class", { "active" => is_active, "disabled" => is_disabled })
|
|
22
|
+
#
|
|
23
|
+
# @example Overriding classes
|
|
24
|
+
# cn("px-4", "px-8") # => "px-8" (later class wins for same property)
|
|
25
|
+
#
|
|
26
|
+
def cn(*args)
|
|
27
|
+
Shadcn::Rails::ClassMerger.merge(*args)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Alias for cn() for those who prefer the full name
|
|
31
|
+
alias_method :class_names, :cn
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Rails
|
|
5
|
+
module Helpers
|
|
6
|
+
# Helper methods for rendering shadcn components
|
|
7
|
+
module ComponentHelper
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
# Render a shadcn component by name
|
|
11
|
+
# This allows for cleaner ERB syntax
|
|
12
|
+
#
|
|
13
|
+
# @param name [Symbol, String] Component name (e.g., :button, :card)
|
|
14
|
+
# @param args [Hash] Component arguments
|
|
15
|
+
# @param block [Proc] Optional block for component content
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# <%= shadcn :button, variant: :primary do %>
|
|
19
|
+
# Click me
|
|
20
|
+
# <% end %>
|
|
21
|
+
#
|
|
22
|
+
def shadcn(name, **args, &block)
|
|
23
|
+
component_class = Shadcn::Rails.component_for(name)
|
|
24
|
+
render(component_class.new(**args), &block)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Shorthand helpers for common components
|
|
28
|
+
# These provide a more Rails-like API
|
|
29
|
+
|
|
30
|
+
def shadcn_button(**args, &block)
|
|
31
|
+
render(Shadcn::ButtonComponent.new(**args), &block)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def shadcn_card(**args, &block)
|
|
35
|
+
render(Shadcn::CardComponent.new(**args), &block)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def shadcn_input(**args)
|
|
39
|
+
render(Shadcn::InputComponent.new(**args))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def shadcn_label(**args, &block)
|
|
43
|
+
render(Shadcn::LabelComponent.new(**args), &block)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def shadcn_badge(**args, &block)
|
|
47
|
+
render(Shadcn::BadgeComponent.new(**args), &block)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def shadcn_alert(**args, &block)
|
|
51
|
+
render(Shadcn::AlertComponent.new(**args), &block)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def shadcn_dialog(**args, &block)
|
|
55
|
+
render(Shadcn::DialogComponent.new(**args), &block)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module Rails
|
|
5
|
+
module Helpers
|
|
6
|
+
# Helper methods for integrating with popular Rails pagination gems
|
|
7
|
+
# Supports: will_paginate, Kaminari, and Pagy
|
|
8
|
+
module PaginationHelper
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
# Render a shadcn pagination component from a paginated collection or Pagy object
|
|
12
|
+
#
|
|
13
|
+
# @param collection_or_pagy [Object] A paginated collection (Kaminari/will_paginate) or Pagy object
|
|
14
|
+
# @param pagy [Pagy, nil] Optional Pagy object (for explicit pagy usage)
|
|
15
|
+
# @param url_builder [Proc, nil] Lambda/Proc to generate page URLs, receives page number
|
|
16
|
+
# @param window [Integer] Number of pages to show around current page (default: 2)
|
|
17
|
+
# @param options [Hash] Additional options passed to PaginationComponent
|
|
18
|
+
#
|
|
19
|
+
# @example With Kaminari
|
|
20
|
+
# <%= shadcn_paginate @users %>
|
|
21
|
+
#
|
|
22
|
+
# @example With will_paginate
|
|
23
|
+
# <%= shadcn_paginate @posts %>
|
|
24
|
+
#
|
|
25
|
+
# @example With Pagy
|
|
26
|
+
# <%= shadcn_paginate @pagy %>
|
|
27
|
+
#
|
|
28
|
+
# @example With custom URL builder
|
|
29
|
+
# <%= shadcn_paginate @posts, url_builder: ->(page) { posts_path(page: page, sort: params[:sort]) } %>
|
|
30
|
+
#
|
|
31
|
+
def shadcn_paginate(collection_or_pagy, pagy: nil, url_builder: nil, window: 2, **options)
|
|
32
|
+
pagination_data = extract_pagination_data(collection_or_pagy, pagy: pagy)
|
|
33
|
+
return nil if pagination_data[:total_pages] <= 1
|
|
34
|
+
|
|
35
|
+
url_builder ||= default_url_builder
|
|
36
|
+
series = generate_page_series(
|
|
37
|
+
pagination_data[:current_page],
|
|
38
|
+
pagination_data[:total_pages],
|
|
39
|
+
window: window
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
render Shadcn::PaginationComponent.new(**options) do |pagination|
|
|
43
|
+
pagination.with_pagination_content do |content|
|
|
44
|
+
# Previous button
|
|
45
|
+
prev_href = pagination_data[:prev_page] ? url_builder.call(pagination_data[:prev_page]) : nil
|
|
46
|
+
content.with_previous(href: prev_href, disabled: pagination_data[:prev_page].nil?)
|
|
47
|
+
|
|
48
|
+
# Page items
|
|
49
|
+
series.each do |item|
|
|
50
|
+
case item
|
|
51
|
+
when :gap
|
|
52
|
+
content.with_ellipse
|
|
53
|
+
when Integer
|
|
54
|
+
content.with_item(href: url_builder.call(item)) { item.to_s }
|
|
55
|
+
when String # Current page (string format)
|
|
56
|
+
page_num = item.to_i
|
|
57
|
+
content.with_item(href: url_builder.call(page_num), active: true) { item }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Next button
|
|
62
|
+
next_href = pagination_data[:next_page] ? url_builder.call(pagination_data[:next_page]) : nil
|
|
63
|
+
content.with_next_page(href: next_href, disabled: pagination_data[:next_page].nil?)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# Extract pagination data from various sources
|
|
71
|
+
# Returns a hash with :current_page, :total_pages, :prev_page, :next_page
|
|
72
|
+
def extract_pagination_data(collection_or_pagy, pagy: nil)
|
|
73
|
+
obj = pagy || collection_or_pagy
|
|
74
|
+
|
|
75
|
+
# Pagy detection (has .page and .pages methods, or is a Pagy instance)
|
|
76
|
+
if defined?(::Pagy) && obj.is_a?(::Pagy)
|
|
77
|
+
{
|
|
78
|
+
current_page: obj.page,
|
|
79
|
+
total_pages: obj.pages,
|
|
80
|
+
prev_page: obj.prev,
|
|
81
|
+
next_page: obj.next
|
|
82
|
+
}
|
|
83
|
+
# Duck typing for Pagy-like objects (has .page and .pages)
|
|
84
|
+
elsif obj.respond_to?(:page) && obj.respond_to?(:pages) && obj.respond_to?(:prev) && obj.respond_to?(:next)
|
|
85
|
+
{
|
|
86
|
+
current_page: obj.page,
|
|
87
|
+
total_pages: obj.pages,
|
|
88
|
+
prev_page: obj.prev,
|
|
89
|
+
next_page: obj.next
|
|
90
|
+
}
|
|
91
|
+
# Kaminari detection (has current_page and total_pages, with prev_page)
|
|
92
|
+
elsif obj.respond_to?(:current_page) && obj.respond_to?(:total_pages) && obj.respond_to?(:prev_page)
|
|
93
|
+
{
|
|
94
|
+
current_page: obj.current_page,
|
|
95
|
+
total_pages: obj.total_pages,
|
|
96
|
+
prev_page: obj.prev_page,
|
|
97
|
+
next_page: obj.next_page
|
|
98
|
+
}
|
|
99
|
+
# will_paginate detection (has current_page and total_pages, with previous_page)
|
|
100
|
+
elsif obj.respond_to?(:current_page) && obj.respond_to?(:total_pages) && obj.respond_to?(:previous_page)
|
|
101
|
+
{
|
|
102
|
+
current_page: obj.current_page,
|
|
103
|
+
total_pages: obj.total_pages,
|
|
104
|
+
prev_page: obj.previous_page,
|
|
105
|
+
next_page: obj.next_page
|
|
106
|
+
}
|
|
107
|
+
# Generic fallback for collections with current_page and total_pages
|
|
108
|
+
elsif obj.respond_to?(:current_page) && obj.respond_to?(:total_pages)
|
|
109
|
+
current = obj.current_page
|
|
110
|
+
total = obj.total_pages
|
|
111
|
+
{
|
|
112
|
+
current_page: current,
|
|
113
|
+
total_pages: total,
|
|
114
|
+
prev_page: current > 1 ? current - 1 : nil,
|
|
115
|
+
next_page: current < total ? current + 1 : nil
|
|
116
|
+
}
|
|
117
|
+
else
|
|
118
|
+
raise ArgumentError, "Expected a paginated collection (Kaminari/will_paginate) or Pagy object. " \
|
|
119
|
+
"Object must respond to current_page/total_pages or page/pages."
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Generate an array of page numbers with gaps
|
|
124
|
+
# Current page is returned as a String, others as Integers, gaps as :gap
|
|
125
|
+
#
|
|
126
|
+
# @example generate_page_series(5, 10, window: 2)
|
|
127
|
+
# => [1, :gap, 3, 4, "5", 6, 7, :gap, 10]
|
|
128
|
+
def generate_page_series(current_page, total_pages, window: 2)
|
|
129
|
+
return [(current_page.to_s)] if total_pages <= 1
|
|
130
|
+
|
|
131
|
+
series = []
|
|
132
|
+
|
|
133
|
+
# Always include first page
|
|
134
|
+
series << 1
|
|
135
|
+
|
|
136
|
+
# Calculate range around current page
|
|
137
|
+
range_start = [2, current_page - window].max
|
|
138
|
+
range_end = [total_pages - 1, current_page + window].min
|
|
139
|
+
|
|
140
|
+
# Add gap before range if needed
|
|
141
|
+
if range_start > 2
|
|
142
|
+
series << :gap
|
|
143
|
+
elsif range_start == 2
|
|
144
|
+
# No gap needed, just add page 2
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Add pages in range (including potential page 2 if range_start is 2)
|
|
148
|
+
(range_start..range_end).each do |page|
|
|
149
|
+
next if page == 1 || page == total_pages # Skip first and last (added separately)
|
|
150
|
+
|
|
151
|
+
if page == current_page
|
|
152
|
+
series << page.to_s
|
|
153
|
+
else
|
|
154
|
+
series << page
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Add gap after range if needed
|
|
159
|
+
if range_end < total_pages - 1
|
|
160
|
+
series << :gap
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Always include last page if more than 1 page
|
|
164
|
+
if total_pages > 1
|
|
165
|
+
if current_page == total_pages
|
|
166
|
+
series << total_pages.to_s
|
|
167
|
+
else
|
|
168
|
+
series << total_pages
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Handle current page being 1 or total_pages
|
|
173
|
+
if current_page == 1
|
|
174
|
+
series[0] = "1"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
series
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Default URL builder using query params
|
|
181
|
+
def default_url_builder
|
|
182
|
+
->(page) { "?page=#{page}" }
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|