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,337 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Calendar component - date picker grid
|
|
5
|
+
# Matches shadcn/ui Calendar component
|
|
6
|
+
#
|
|
7
|
+
# @example Basic calendar
|
|
8
|
+
# <%= render Shadcn::CalendarComponent.new %>
|
|
9
|
+
#
|
|
10
|
+
# @example With selected date
|
|
11
|
+
# <%= render Shadcn::CalendarComponent.new(selected: Date.today) %>
|
|
12
|
+
#
|
|
13
|
+
# @example With name for form submission
|
|
14
|
+
# <%= render Shadcn::CalendarComponent.new(name: "event[date]") %>
|
|
15
|
+
#
|
|
16
|
+
class CalendarComponent < BaseComponent
|
|
17
|
+
CONTAINER_CLASSES = "p-3 rounded-md border bg-background"
|
|
18
|
+
HEADER_CLASSES = "flex items-center justify-between mb-4"
|
|
19
|
+
MONTH_YEAR_CLASSES = "text-sm font-medium"
|
|
20
|
+
NAV_BUTTON_CLASSES = "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground h-7 w-7"
|
|
21
|
+
WEEKDAY_CLASSES = "text-center text-xs font-medium text-muted-foreground"
|
|
22
|
+
DAY_CLASSES = "h-8 w-8 text-center text-sm p-0 relative flex items-center justify-center rounded-md cursor-pointer hover:bg-accent hover:text-accent-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
|
23
|
+
DAY_SELECTED_CLASSES = "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground"
|
|
24
|
+
DAY_TODAY_CLASSES = "bg-accent text-accent-foreground"
|
|
25
|
+
DAY_OUTSIDE_CLASSES = "text-muted-foreground opacity-50"
|
|
26
|
+
DAY_DISABLED_CLASSES = "text-muted-foreground opacity-50 pointer-events-none"
|
|
27
|
+
|
|
28
|
+
WEEKDAYS = %w[Su Mo Tu We Th Fr Sa].freeze
|
|
29
|
+
# Mapping for Rails beginning_of_week symbols
|
|
30
|
+
WEEK_START_SYMBOLS = {
|
|
31
|
+
0 => :sunday,
|
|
32
|
+
1 => :monday,
|
|
33
|
+
2 => :tuesday,
|
|
34
|
+
3 => :wednesday,
|
|
35
|
+
4 => :thursday,
|
|
36
|
+
5 => :friday,
|
|
37
|
+
6 => :saturday
|
|
38
|
+
}.freeze
|
|
39
|
+
MONTHS = %w[January February March April May June July August September October November December].freeze
|
|
40
|
+
|
|
41
|
+
MODES = %i[single multiple range].freeze
|
|
42
|
+
|
|
43
|
+
# @param selected [Date, Array<Date>, nil] Currently selected date(s)
|
|
44
|
+
# @param month [Date, nil] Month to display (defaults to current month)
|
|
45
|
+
# @param min_date [Date, nil] Minimum selectable date
|
|
46
|
+
# @param max_date [Date, nil] Maximum selectable date
|
|
47
|
+
# @param name [String, nil] Form field name for hidden input
|
|
48
|
+
# @param disabled_dates [Array<Date>] Specific dates that cannot be selected
|
|
49
|
+
# @param disabled_days_of_week [Array<Integer>] Days of week to disable (0=Sun, 6=Sat)
|
|
50
|
+
# @param show_outside_days [Boolean] Whether to show days outside current month
|
|
51
|
+
# @param mode [Symbol] Selection mode: :single, :multiple, or :range
|
|
52
|
+
# @param required [Boolean] Whether a selection is required (prevents deselection)
|
|
53
|
+
# @param week_starts_on [Integer] First day of week (0=Sunday, 1=Monday, etc.)
|
|
54
|
+
def initialize(
|
|
55
|
+
selected: nil,
|
|
56
|
+
month: nil,
|
|
57
|
+
min_date: nil,
|
|
58
|
+
max_date: nil,
|
|
59
|
+
name: nil,
|
|
60
|
+
disabled_dates: [],
|
|
61
|
+
disabled_days_of_week: [],
|
|
62
|
+
show_outside_days: true,
|
|
63
|
+
mode: :single,
|
|
64
|
+
required: false,
|
|
65
|
+
week_starts_on: 0,
|
|
66
|
+
**options
|
|
67
|
+
)
|
|
68
|
+
super(**options)
|
|
69
|
+
@selected = selected
|
|
70
|
+
@month = month || (selected.is_a?(Array) ? selected.first : selected) || Date.today
|
|
71
|
+
@min_date = min_date
|
|
72
|
+
@max_date = max_date
|
|
73
|
+
@name = name
|
|
74
|
+
@disabled_dates = disabled_dates
|
|
75
|
+
@disabled_days_of_week = disabled_days_of_week
|
|
76
|
+
@show_outside_days = show_outside_days
|
|
77
|
+
@mode = mode.to_sym
|
|
78
|
+
@required = required
|
|
79
|
+
@week_starts_on = week_starts_on
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def call
|
|
83
|
+
content_tag(:div, calendar_content, **calendar_attributes)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def calendar_content
|
|
89
|
+
safe_join([
|
|
90
|
+
hidden_input,
|
|
91
|
+
header,
|
|
92
|
+
weekday_header,
|
|
93
|
+
days_grid
|
|
94
|
+
].compact)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def hidden_input
|
|
98
|
+
return unless @name
|
|
99
|
+
|
|
100
|
+
tag.input(
|
|
101
|
+
type: "hidden",
|
|
102
|
+
name: @name,
|
|
103
|
+
value: @selected&.iso8601,
|
|
104
|
+
data: { "shadcn--calendar-target": "hiddenInput" }
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def header
|
|
109
|
+
content_tag(:div, class: HEADER_CLASSES) do
|
|
110
|
+
safe_join([
|
|
111
|
+
prev_button,
|
|
112
|
+
month_year_selectors,
|
|
113
|
+
next_button
|
|
114
|
+
])
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def prev_button
|
|
119
|
+
content_tag(:button,
|
|
120
|
+
chevron_left_icon,
|
|
121
|
+
type: "button",
|
|
122
|
+
class: NAV_BUTTON_CLASSES,
|
|
123
|
+
"aria-label": "Previous month",
|
|
124
|
+
data: { action: "click->shadcn--calendar#previousMonth" }
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def next_button
|
|
129
|
+
content_tag(:button,
|
|
130
|
+
chevron_right_icon,
|
|
131
|
+
type: "button",
|
|
132
|
+
class: NAV_BUTTON_CLASSES,
|
|
133
|
+
"aria-label": "Next month",
|
|
134
|
+
data: { action: "click->shadcn--calendar#nextMonth" }
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def month_year_selectors
|
|
139
|
+
content_tag(:div, class: "flex items-center gap-1") do
|
|
140
|
+
safe_join([
|
|
141
|
+
month_select,
|
|
142
|
+
year_select
|
|
143
|
+
])
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def month_select
|
|
148
|
+
content_tag(:select,
|
|
149
|
+
class: cn(
|
|
150
|
+
"appearance-none bg-transparent text-sm font-medium cursor-pointer",
|
|
151
|
+
"hover:bg-accent hover:text-accent-foreground rounded px-2 py-1",
|
|
152
|
+
"focus:outline-none focus:ring-1 focus:ring-ring"
|
|
153
|
+
),
|
|
154
|
+
data: {
|
|
155
|
+
"shadcn--calendar-target": "monthSelect",
|
|
156
|
+
action: "change->shadcn--calendar#selectMonth"
|
|
157
|
+
}
|
|
158
|
+
) do
|
|
159
|
+
safe_join(MONTHS.each_with_index.map { |month, index|
|
|
160
|
+
content_tag(:option, month, value: index, selected: index == @month.month - 1)
|
|
161
|
+
})
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def year_select
|
|
166
|
+
# Generate year range: current year -10 to +10
|
|
167
|
+
current_year = @month.year
|
|
168
|
+
year_range = (current_year - 10)..(current_year + 10)
|
|
169
|
+
|
|
170
|
+
content_tag(:select,
|
|
171
|
+
class: cn(
|
|
172
|
+
"appearance-none bg-transparent text-sm font-medium cursor-pointer",
|
|
173
|
+
"hover:bg-accent hover:text-accent-foreground rounded px-2 py-1",
|
|
174
|
+
"focus:outline-none focus:ring-1 focus:ring-ring"
|
|
175
|
+
),
|
|
176
|
+
data: {
|
|
177
|
+
"shadcn--calendar-target": "yearSelect",
|
|
178
|
+
action: "change->shadcn--calendar#selectYear"
|
|
179
|
+
}
|
|
180
|
+
) do
|
|
181
|
+
safe_join(year_range.map { |year|
|
|
182
|
+
content_tag(:option, year, value: year, selected: year == current_year)
|
|
183
|
+
})
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def weekday_header
|
|
188
|
+
content_tag(:div, class: "grid grid-cols-7 gap-1 mb-2") do
|
|
189
|
+
safe_join(rotated_weekdays.map { |day| content_tag(:div, day, class: WEEKDAY_CLASSES) })
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def rotated_weekdays
|
|
194
|
+
WEEKDAYS.rotate(@week_starts_on)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def days_grid
|
|
198
|
+
content_tag(:div, class: "grid grid-cols-7 gap-1", data: { "shadcn--calendar-target": "grid" }) do
|
|
199
|
+
safe_join(calendar_days.map { |day| render_day(day) })
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def calendar_days
|
|
204
|
+
first_day = @month.beginning_of_month
|
|
205
|
+
last_day = @month.end_of_month
|
|
206
|
+
|
|
207
|
+
# Get the week start symbol from the mapping (defaults to :sunday)
|
|
208
|
+
week_start_symbol = WEEK_START_SYMBOLS[@week_starts_on] || :sunday
|
|
209
|
+
|
|
210
|
+
# Get the starting day based on week_starts_on
|
|
211
|
+
start_date = first_day.beginning_of_week(week_start_symbol)
|
|
212
|
+
# Get the ending day based on week_starts_on
|
|
213
|
+
end_date = last_day.end_of_week(week_start_symbol)
|
|
214
|
+
|
|
215
|
+
(start_date..end_date).to_a
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def render_day(date)
|
|
219
|
+
is_outside = date.month != @month.month
|
|
220
|
+
is_selected = @selected && date == @selected
|
|
221
|
+
is_today = date == Date.today
|
|
222
|
+
is_disabled = date_disabled?(date)
|
|
223
|
+
|
|
224
|
+
return empty_day if is_outside && !@show_outside_days
|
|
225
|
+
|
|
226
|
+
classes = [DAY_CLASSES]
|
|
227
|
+
classes << DAY_SELECTED_CLASSES if is_selected
|
|
228
|
+
classes << DAY_TODAY_CLASSES if is_today && !is_selected
|
|
229
|
+
classes << DAY_OUTSIDE_CLASSES if is_outside
|
|
230
|
+
classes << DAY_DISABLED_CLASSES if is_disabled
|
|
231
|
+
|
|
232
|
+
content_tag(:button,
|
|
233
|
+
date.day.to_s,
|
|
234
|
+
type: "button",
|
|
235
|
+
class: cn(*classes),
|
|
236
|
+
tabindex: is_disabled ? "-1" : "0",
|
|
237
|
+
"aria-selected": is_selected || nil,
|
|
238
|
+
"aria-disabled": is_disabled || nil,
|
|
239
|
+
disabled: is_disabled || nil,
|
|
240
|
+
data: {
|
|
241
|
+
date: date.iso8601,
|
|
242
|
+
"shadcn--calendar-target": "day",
|
|
243
|
+
action: is_disabled ? nil : "click->shadcn--calendar#selectDay"
|
|
244
|
+
}.compact
|
|
245
|
+
)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def empty_day
|
|
249
|
+
content_tag(:div, "", class: "h-8 w-8")
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def date_disabled?(date)
|
|
253
|
+
return true if @min_date && date < @min_date
|
|
254
|
+
return true if @max_date && date > @max_date
|
|
255
|
+
return true if @disabled_dates.include?(date)
|
|
256
|
+
return true if @disabled_days_of_week.include?(date.wday)
|
|
257
|
+
|
|
258
|
+
false
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def chevron_left_icon
|
|
262
|
+
content_tag(:svg,
|
|
263
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
264
|
+
width: "16",
|
|
265
|
+
height: "16",
|
|
266
|
+
viewBox: "0 0 24 24",
|
|
267
|
+
fill: "none",
|
|
268
|
+
stroke: "currentColor",
|
|
269
|
+
"stroke-width": "2",
|
|
270
|
+
"stroke-linecap": "round",
|
|
271
|
+
"stroke-linejoin": "round"
|
|
272
|
+
) do
|
|
273
|
+
tag.path(d: "m15 18-6-6 6-6")
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def chevron_right_icon
|
|
278
|
+
content_tag(:svg,
|
|
279
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
280
|
+
width: "16",
|
|
281
|
+
height: "16",
|
|
282
|
+
viewBox: "0 0 24 24",
|
|
283
|
+
fill: "none",
|
|
284
|
+
stroke: "currentColor",
|
|
285
|
+
"stroke-width": "2",
|
|
286
|
+
"stroke-linecap": "round",
|
|
287
|
+
"stroke-linejoin": "round"
|
|
288
|
+
) do
|
|
289
|
+
tag.path(d: "m9 18 6-6-6-6")
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def calendar_attributes
|
|
294
|
+
{
|
|
295
|
+
class: cn(CONTAINER_CLASSES, class_name),
|
|
296
|
+
role: "grid",
|
|
297
|
+
"aria-label": "Calendar",
|
|
298
|
+
data: stimulus_data
|
|
299
|
+
}.merge(html_options).merge(build_data)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def stimulus_data
|
|
303
|
+
data = {
|
|
304
|
+
controller: "shadcn--calendar",
|
|
305
|
+
"shadcn--calendar-month-value": @month.iso8601,
|
|
306
|
+
"shadcn--calendar-selected-value": format_selected_value,
|
|
307
|
+
"shadcn--calendar-mode-value": @mode.to_s,
|
|
308
|
+
"shadcn--calendar-required-value": @required.to_s,
|
|
309
|
+
"shadcn--calendar-week-starts-on-value": @week_starts_on.to_s,
|
|
310
|
+
"shadcn--calendar-show-outside-days-value": @show_outside_days.to_s
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
# Add optional values only if present
|
|
314
|
+
data["shadcn--calendar-min-date-value"] = @min_date.iso8601 if @min_date
|
|
315
|
+
data["shadcn--calendar-max-date-value"] = @max_date.iso8601 if @max_date
|
|
316
|
+
data["shadcn--calendar-disabled-dates-value"] = format_disabled_dates if @disabled_dates.any?
|
|
317
|
+
data["shadcn--calendar-disabled-days-of-week-value"] = @disabled_days_of_week.join(",") if @disabled_days_of_week.any?
|
|
318
|
+
|
|
319
|
+
data
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def format_selected_value
|
|
323
|
+
return nil unless @selected
|
|
324
|
+
|
|
325
|
+
case @selected
|
|
326
|
+
when Array
|
|
327
|
+
@selected.map(&:iso8601).join(",")
|
|
328
|
+
else
|
|
329
|
+
@selected.iso8601
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def format_disabled_dates
|
|
334
|
+
@disabled_dates.map(&:iso8601).join(",")
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Card component with header, content, and footer slots
|
|
5
|
+
# Matches shadcn/ui Card component
|
|
6
|
+
#
|
|
7
|
+
# @example Basic card
|
|
8
|
+
# <%= render Shadcn::CardComponent.new do |card| %>
|
|
9
|
+
# <% card.with_header do %>
|
|
10
|
+
# <% card.with_title { "Card Title" } %>
|
|
11
|
+
# <% card.with_description { "Card description" } %>
|
|
12
|
+
# <% end %>
|
|
13
|
+
# <% card.with_content do %>
|
|
14
|
+
# Card content goes here
|
|
15
|
+
# <% end %>
|
|
16
|
+
# <% card.with_footer do %>
|
|
17
|
+
# <button>Action</button>
|
|
18
|
+
# <% end %>
|
|
19
|
+
# <% end %>
|
|
20
|
+
#
|
|
21
|
+
class CardComponent < BaseComponent
|
|
22
|
+
# Card header slot
|
|
23
|
+
renders_one :header, lambda { |**options, &block|
|
|
24
|
+
CardHeaderComponent.new(**options, &block)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Card title slot (can be used inside or outside header)
|
|
28
|
+
renders_one :title, lambda { |**options, &block|
|
|
29
|
+
CardTitleComponent.new(**options, &block)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Card description slot
|
|
33
|
+
renders_one :description, lambda { |**options, &block|
|
|
34
|
+
CardDescriptionComponent.new(**options, &block)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Card content slot
|
|
38
|
+
# Note: Named content_slot because 'content' is a reserved ViewComponent method
|
|
39
|
+
renders_one :content_slot, lambda { |**options, &block|
|
|
40
|
+
CardContentComponent.new(**options, &block)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Alias for more intuitive API: card.with_content instead of card.with_content_slot
|
|
44
|
+
alias_method :with_content, :with_content_slot
|
|
45
|
+
|
|
46
|
+
# Card footer slot
|
|
47
|
+
renders_one :footer, lambda { |**options, &block|
|
|
48
|
+
CardFooterComponent.new(**options, &block)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
BASE_CLASSES = "rounded-xl border bg-card text-card-foreground shadow"
|
|
52
|
+
|
|
53
|
+
def call
|
|
54
|
+
content_tag(:div, card_content, class: merge_classes(BASE_CLASSES), **html_options.merge(build_data))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def card_content
|
|
60
|
+
safe_join([header, title, description, content_slot, content, footer].compact)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Card Content component
|
|
5
|
+
class CardContentComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "p-6"
|
|
7
|
+
|
|
8
|
+
# @param standalone [Boolean] Whether the content is standalone (no header above)
|
|
9
|
+
def initialize(standalone: false, **options, &block)
|
|
10
|
+
super(**options, &block)
|
|
11
|
+
@standalone = standalone
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
classes = @standalone ? BASE_CLASSES : "#{BASE_CLASSES} pt-0"
|
|
16
|
+
content_tag(:div, content, class: merge_classes(classes), **html_options)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Card Description component
|
|
5
|
+
class CardDescriptionComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "text-sm text-muted-foreground"
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
content_tag(:p, content, class: merge_classes(BASE_CLASSES), **html_options)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Card Footer component
|
|
5
|
+
class CardFooterComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "flex items-center p-6 pt-0"
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
content_tag(:div, content, class: merge_classes(BASE_CLASSES), **html_options)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Card Header component
|
|
5
|
+
class CardHeaderComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "flex flex-col space-y-1.5 p-6"
|
|
7
|
+
|
|
8
|
+
renders_one :title, lambda { |**options|
|
|
9
|
+
CardTitleComponent.new(**options)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
renders_one :description, lambda { |**options|
|
|
13
|
+
CardDescriptionComponent.new(**options)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
renders_one :action, "CardActionComponent"
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
content_tag(:div, class: merge_classes(BASE_CLASSES), **html_options) do
|
|
20
|
+
safe_join([title, description, action, content].compact)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
# Card Title component
|
|
5
|
+
class CardTitleComponent < BaseComponent
|
|
6
|
+
BASE_CLASSES = "font-semibold leading-none tracking-tight"
|
|
7
|
+
|
|
8
|
+
# @param tag [Symbol] HTML tag to use (default: :h3)
|
|
9
|
+
def initialize(tag: :h3, **options, &block)
|
|
10
|
+
super(**options, &block)
|
|
11
|
+
@tag = tag
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
content_tag(@tag, content, class: merge_classes(BASE_CLASSES), **html_options)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|