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,203 @@
|
|
|
1
|
+
import { Application } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helper to set up Stimulus controller tests
|
|
5
|
+
* Creates a DOM element with the controller connected
|
|
6
|
+
*/
|
|
7
|
+
export function setupController(Controller, html, controllerName = 'test') {
|
|
8
|
+
const application = Application.start()
|
|
9
|
+
application.register(controllerName, Controller)
|
|
10
|
+
|
|
11
|
+
document.body.innerHTML = html
|
|
12
|
+
|
|
13
|
+
// Wait for Stimulus to connect the controller
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
requestAnimationFrame(() => {
|
|
16
|
+
const element = document.querySelector(`[data-controller="${controllerName}"]`)
|
|
17
|
+
const controller = application.getControllerForElementAndIdentifier(element, controllerName)
|
|
18
|
+
resolve({ application, element, controller })
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Clean up after tests
|
|
25
|
+
*/
|
|
26
|
+
export function cleanupController(application) {
|
|
27
|
+
if (application) {
|
|
28
|
+
application.stop()
|
|
29
|
+
}
|
|
30
|
+
document.body.innerHTML = ''
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Simulate a click event on an element
|
|
35
|
+
*/
|
|
36
|
+
export function click(element) {
|
|
37
|
+
element.dispatchEvent(new MouseEvent('click', {
|
|
38
|
+
bubbles: true,
|
|
39
|
+
cancelable: true,
|
|
40
|
+
view: window
|
|
41
|
+
}))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Wait for a specified number of milliseconds
|
|
46
|
+
*/
|
|
47
|
+
export function wait(ms) {
|
|
48
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Wait for an animation frame
|
|
53
|
+
*/
|
|
54
|
+
export function nextFrame() {
|
|
55
|
+
return new Promise(resolve => requestAnimationFrame(resolve))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Simulate a keyboard event on an element
|
|
60
|
+
* @param {Element} element - Target element
|
|
61
|
+
* @param {string} key - Key name (e.g., 'ArrowDown', 'Enter', 'Escape')
|
|
62
|
+
* @param {Object} options - Additional options (shiftKey, ctrlKey, etc.)
|
|
63
|
+
*/
|
|
64
|
+
export function keydown(element, key, options = {}) {
|
|
65
|
+
element.dispatchEvent(new KeyboardEvent('keydown', {
|
|
66
|
+
key,
|
|
67
|
+
bubbles: true,
|
|
68
|
+
cancelable: true,
|
|
69
|
+
...options
|
|
70
|
+
}))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Simulate a keyup event
|
|
75
|
+
*/
|
|
76
|
+
export function keyup(element, key, options = {}) {
|
|
77
|
+
element.dispatchEvent(new KeyboardEvent('keyup', {
|
|
78
|
+
key,
|
|
79
|
+
bubbles: true,
|
|
80
|
+
cancelable: true,
|
|
81
|
+
...options
|
|
82
|
+
}))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Wait for a portal element to appear in the DOM
|
|
87
|
+
* @param {string} selector - CSS selector for the portal
|
|
88
|
+
* @param {boolean} shouldExist - Whether portal should exist (true) or not exist (false)
|
|
89
|
+
* @param {number} timeout - Maximum wait time in ms
|
|
90
|
+
*/
|
|
91
|
+
export async function waitForPortal(selector, shouldExist = true, timeout = 1000) {
|
|
92
|
+
const startTime = Date.now()
|
|
93
|
+
|
|
94
|
+
while (Date.now() - startTime < timeout) {
|
|
95
|
+
const element = document.querySelector(selector)
|
|
96
|
+
if (shouldExist && element) return element
|
|
97
|
+
if (!shouldExist && !element) return null
|
|
98
|
+
await wait(10)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new Error(`Portal ${selector} ${shouldExist ? 'did not appear' : 'did not disappear'} within ${timeout}ms`)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Mock window.location for URL-related tests
|
|
106
|
+
* Uses jsdom's internal reconfigure or modifies location properties
|
|
107
|
+
* @param {string} url - The URL to mock
|
|
108
|
+
* @returns {Function} Cleanup function to restore original location
|
|
109
|
+
*/
|
|
110
|
+
export function mockLocation(url) {
|
|
111
|
+
// Store original href
|
|
112
|
+
const originalHref = window.location.href
|
|
113
|
+
|
|
114
|
+
// Use jsdom's reconfigure if available (available in newer jsdom)
|
|
115
|
+
if (typeof window._virtualConsole !== 'undefined' && window.location._setHref) {
|
|
116
|
+
window.location._setHref(url)
|
|
117
|
+
return () => {
|
|
118
|
+
if (window.location._setHref) {
|
|
119
|
+
window.location._setHref(originalHref)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Fallback: Use history API to change URL without navigation
|
|
125
|
+
const urlObj = new URL(url, window.location.origin)
|
|
126
|
+
window.history.replaceState({}, '', urlObj.href)
|
|
127
|
+
|
|
128
|
+
return () => {
|
|
129
|
+
window.history.replaceState({}, '', originalHref)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Mock history.pushState and replaceState for URL sync tests
|
|
135
|
+
*/
|
|
136
|
+
export function mockHistory() {
|
|
137
|
+
const originalPushState = window.history.pushState
|
|
138
|
+
const originalReplaceState = window.history.replaceState
|
|
139
|
+
|
|
140
|
+
const calls = {
|
|
141
|
+
pushState: [],
|
|
142
|
+
replaceState: []
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
window.history.pushState = (state, title, url) => {
|
|
146
|
+
calls.pushState.push({ state, title, url })
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
window.history.replaceState = (state, title, url) => {
|
|
150
|
+
calls.replaceState.push({ state, title, url })
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
calls,
|
|
155
|
+
restore: () => {
|
|
156
|
+
window.history.pushState = originalPushState
|
|
157
|
+
window.history.replaceState = originalReplaceState
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get all focusable elements within a container
|
|
164
|
+
* @param {Element} container - Container element
|
|
165
|
+
*/
|
|
166
|
+
export function getFocusableElements(container) {
|
|
167
|
+
return container.querySelectorAll(
|
|
168
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Dispatch a custom event
|
|
174
|
+
* @param {Element} element - Target element
|
|
175
|
+
* @param {string} eventName - Event name
|
|
176
|
+
* @param {Object} detail - Event detail object
|
|
177
|
+
*/
|
|
178
|
+
export function dispatchEvent(element, eventName, detail = {}) {
|
|
179
|
+
element.dispatchEvent(new CustomEvent(eventName, {
|
|
180
|
+
bubbles: true,
|
|
181
|
+
cancelable: true,
|
|
182
|
+
detail
|
|
183
|
+
}))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Wait for controller to emit a specific event
|
|
188
|
+
* @param {Element} element - Element to listen on
|
|
189
|
+
* @param {string} eventName - Event name (e.g., 'shadcn--accordion:expand')
|
|
190
|
+
* @param {number} timeout - Maximum wait time
|
|
191
|
+
*/
|
|
192
|
+
export function waitForEvent(element, eventName, timeout = 1000) {
|
|
193
|
+
return new Promise((resolve, reject) => {
|
|
194
|
+
const timer = setTimeout(() => {
|
|
195
|
+
reject(new Error(`Event ${eventName} not received within ${timeout}ms`))
|
|
196
|
+
}, timeout)
|
|
197
|
+
|
|
198
|
+
element.addEventListener(eventName, (event) => {
|
|
199
|
+
clearTimeout(timer)
|
|
200
|
+
resolve(event)
|
|
201
|
+
}, { once: true })
|
|
202
|
+
})
|
|
203
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//= link_tree ../stylesheets .css
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Accordion controller for collapsible sections
|
|
5
|
+
* Supports single and multiple expansion modes
|
|
6
|
+
*/
|
|
7
|
+
export default class AccordionController extends Controller {
|
|
8
|
+
static targets: ["item", "trigger", "content"];
|
|
9
|
+
static values: {
|
|
10
|
+
type: { type: "String"; default: "single" };
|
|
11
|
+
collapsible: { type: "Boolean"; default: false };
|
|
12
|
+
default: { type: "String"; default: "" };
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Accordion item targets */
|
|
16
|
+
readonly itemTargets: HTMLElement[];
|
|
17
|
+
readonly hasItemTarget: boolean;
|
|
18
|
+
|
|
19
|
+
/** Accordion trigger targets */
|
|
20
|
+
readonly triggerTargets: HTMLElement[];
|
|
21
|
+
readonly hasTriggerTarget: boolean;
|
|
22
|
+
|
|
23
|
+
/** Accordion content targets */
|
|
24
|
+
readonly contentTargets: HTMLElement[];
|
|
25
|
+
readonly hasContentTarget: boolean;
|
|
26
|
+
|
|
27
|
+
/** Expansion type: "single" or "multiple" */
|
|
28
|
+
typeValue: "single" | "multiple";
|
|
29
|
+
readonly hasTypeValue: boolean;
|
|
30
|
+
|
|
31
|
+
/** Whether single items can be collapsed */
|
|
32
|
+
collapsibleValue: boolean;
|
|
33
|
+
readonly hasCollapsibleValue: boolean;
|
|
34
|
+
|
|
35
|
+
/** Default expanded items (comma-separated values for multiple) */
|
|
36
|
+
defaultValue: string;
|
|
37
|
+
readonly hasDefaultValue: boolean;
|
|
38
|
+
|
|
39
|
+
/** Toggle an accordion item open/closed */
|
|
40
|
+
toggle(event: Event): void;
|
|
41
|
+
|
|
42
|
+
/** Expand a specific item */
|
|
43
|
+
expandItem(item: HTMLElement): void;
|
|
44
|
+
|
|
45
|
+
/** Collapse a specific item */
|
|
46
|
+
collapseItem(item: HTMLElement): void;
|
|
47
|
+
|
|
48
|
+
/** Find an item by its value */
|
|
49
|
+
findItemByValue(value: string): HTMLElement | undefined;
|
|
50
|
+
|
|
51
|
+
/** Handle keyboard navigation */
|
|
52
|
+
handleKeydown(event: KeyboardEvent): void;
|
|
53
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Accordion controller for collapsible sections
|
|
5
|
+
* Supports single and multiple expansion modes
|
|
6
|
+
*/
|
|
7
|
+
export default class extends Controller {
|
|
8
|
+
static targets = ["item", "trigger", "content"]
|
|
9
|
+
static values = {
|
|
10
|
+
type: { type: String, default: "single" }, // "single" or "multiple"
|
|
11
|
+
collapsible: { type: Boolean, default: false },
|
|
12
|
+
default: { type: String, default: "" } // comma-separated values for multiple
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
connect() {
|
|
16
|
+
// Expand default items
|
|
17
|
+
if (this.defaultValue) {
|
|
18
|
+
const defaultValues = this.defaultValue.split(",").map(v => v.trim())
|
|
19
|
+
defaultValues.forEach(value => {
|
|
20
|
+
const item = this.findItemByValue(value)
|
|
21
|
+
if (item) {
|
|
22
|
+
this.expandItem(item)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
toggle(event) {
|
|
29
|
+
const trigger = event.currentTarget
|
|
30
|
+
const item = trigger.closest('[data-shadcn--accordion-target="item"]')
|
|
31
|
+
|
|
32
|
+
if (!item) return
|
|
33
|
+
|
|
34
|
+
const isOpen = item.dataset.state === "open"
|
|
35
|
+
|
|
36
|
+
if (isOpen) {
|
|
37
|
+
if (this.collapsibleValue || this.typeValue === "multiple") {
|
|
38
|
+
this.collapseItem(item)
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
if (this.typeValue === "single") {
|
|
42
|
+
// Collapse all other items first
|
|
43
|
+
this.itemTargets.forEach(otherItem => {
|
|
44
|
+
if (otherItem !== item && otherItem.dataset.state === "open") {
|
|
45
|
+
this.collapseItem(otherItem)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
this.expandItem(item)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
expandItem(item) {
|
|
54
|
+
const trigger = item.querySelector('[data-shadcn--accordion-target="trigger"]')
|
|
55
|
+
const content = item.querySelector('[data-shadcn--accordion-target="content"]')
|
|
56
|
+
|
|
57
|
+
if (!trigger || !content) return
|
|
58
|
+
|
|
59
|
+
item.dataset.state = "open"
|
|
60
|
+
trigger.dataset.state = "open"
|
|
61
|
+
trigger.setAttribute("aria-expanded", "true")
|
|
62
|
+
content.dataset.state = "open"
|
|
63
|
+
content.hidden = false
|
|
64
|
+
|
|
65
|
+
// Animate height
|
|
66
|
+
const height = content.scrollHeight
|
|
67
|
+
content.style.height = "0px"
|
|
68
|
+
requestAnimationFrame(() => {
|
|
69
|
+
content.style.height = `${height}px`
|
|
70
|
+
// Remove fixed height after animation
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
content.style.height = ""
|
|
73
|
+
}, 200)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
this.dispatch("expand", { detail: { value: item.dataset.value } })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
collapseItem(item) {
|
|
80
|
+
const trigger = item.querySelector('[data-shadcn--accordion-target="trigger"]')
|
|
81
|
+
const content = item.querySelector('[data-shadcn--accordion-target="content"]')
|
|
82
|
+
|
|
83
|
+
if (!trigger || !content) return
|
|
84
|
+
|
|
85
|
+
// Set current height for animation
|
|
86
|
+
content.style.height = `${content.scrollHeight}px`
|
|
87
|
+
|
|
88
|
+
requestAnimationFrame(() => {
|
|
89
|
+
item.dataset.state = "closed"
|
|
90
|
+
trigger.dataset.state = "closed"
|
|
91
|
+
trigger.setAttribute("aria-expanded", "false")
|
|
92
|
+
content.dataset.state = "closed"
|
|
93
|
+
content.style.height = "0px"
|
|
94
|
+
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
content.hidden = true
|
|
97
|
+
content.style.height = ""
|
|
98
|
+
}, 200)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
this.dispatch("collapse", { detail: { value: item.dataset.value } })
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
findItemByValue(value) {
|
|
105
|
+
return this.itemTargets.find(item => item.dataset.value === value)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Keyboard navigation
|
|
109
|
+
handleKeydown(event) {
|
|
110
|
+
const triggers = this.triggerTargets
|
|
111
|
+
const currentIndex = triggers.findIndex(t => t === document.activeElement)
|
|
112
|
+
|
|
113
|
+
if (currentIndex === -1) return
|
|
114
|
+
|
|
115
|
+
let newIndex = currentIndex
|
|
116
|
+
|
|
117
|
+
switch (event.key) {
|
|
118
|
+
case "ArrowUp":
|
|
119
|
+
event.preventDefault()
|
|
120
|
+
newIndex = currentIndex === 0 ? triggers.length - 1 : currentIndex - 1
|
|
121
|
+
break
|
|
122
|
+
case "ArrowDown":
|
|
123
|
+
event.preventDefault()
|
|
124
|
+
newIndex = currentIndex === triggers.length - 1 ? 0 : currentIndex + 1
|
|
125
|
+
break
|
|
126
|
+
case "Home":
|
|
127
|
+
event.preventDefault()
|
|
128
|
+
newIndex = 0
|
|
129
|
+
break
|
|
130
|
+
case "End":
|
|
131
|
+
event.preventDefault()
|
|
132
|
+
newIndex = triggers.length - 1
|
|
133
|
+
break
|
|
134
|
+
default:
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
triggers[newIndex].focus()
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Avatar controller for handling image load errors
|
|
5
|
+
*/
|
|
6
|
+
export default class AvatarController extends Controller {
|
|
7
|
+
static targets: ["image", "fallback"];
|
|
8
|
+
|
|
9
|
+
/** Avatar image target */
|
|
10
|
+
readonly imageTarget: HTMLImageElement;
|
|
11
|
+
readonly hasImageTarget: boolean;
|
|
12
|
+
|
|
13
|
+
/** Fallback content target */
|
|
14
|
+
readonly fallbackTarget: HTMLElement;
|
|
15
|
+
readonly hasFallbackTarget: boolean;
|
|
16
|
+
|
|
17
|
+
/** Handle image load error - shows fallback */
|
|
18
|
+
handleError(): void;
|
|
19
|
+
|
|
20
|
+
/** Handle successful image load - shows image */
|
|
21
|
+
handleLoad(): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Avatar controller for handling image load errors
|
|
5
|
+
*/
|
|
6
|
+
export default class extends Controller {
|
|
7
|
+
static targets = ["image", "fallback"]
|
|
8
|
+
|
|
9
|
+
handleError() {
|
|
10
|
+
if (this.hasImageTarget) {
|
|
11
|
+
this.imageTarget.hidden = true
|
|
12
|
+
}
|
|
13
|
+
if (this.hasFallbackTarget) {
|
|
14
|
+
this.fallbackTarget.classList.remove("hidden")
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
handleLoad() {
|
|
19
|
+
if (this.hasImageTarget) {
|
|
20
|
+
this.imageTarget.hidden = false
|
|
21
|
+
}
|
|
22
|
+
if (this.hasFallbackTarget) {
|
|
23
|
+
this.fallbackTarget.classList.add("hidden")
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|