wilday_ui 0.8.0 → 0.9.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 +4 -4
- data/README.md +3 -2
- data/app/assets/builds/wilday_ui/index.js +78 -27
- data/app/assets/builds/wilday_ui/index.js.map +3 -3
- data/app/assets/stylesheets/wilday_ui/components/button/base.css +12 -0
- data/app/assets/stylesheets/wilday_ui/components/button/features/animation.css +284 -0
- data/app/assets/stylesheets/wilday_ui/components/button/features/sizes.css +36 -0
- data/app/assets/stylesheets/wilday_ui/components/button/index.css +1 -0
- data/app/helpers/wilday_ui/application_helper.rb +1 -1
- data/app/helpers/wilday_ui/components/button/button_helper.rb +348 -0
- data/app/helpers/wilday_ui/components/button/feature_engine.rb +74 -0
- data/app/helpers/wilday_ui/components/button/features/animation.rb +97 -0
- data/app/helpers/wilday_ui/components/button/features/confirm_dialog.rb +70 -0
- data/app/helpers/wilday_ui/components/button/features/copy_to_clipboard.rb +56 -0
- data/app/helpers/wilday_ui/components/button/features/dropdown.rb +74 -0
- data/app/helpers/wilday_ui/components/button/features/loading.rb +32 -0
- data/app/helpers/wilday_ui/components/button/features/tooltip.rb +138 -0
- data/app/javascript/wilday_ui/controllers/animation_controller.js +97 -0
- data/app/javascript/wilday_ui/controllers/index.js +2 -0
- data/app/javascript/wilday_ui/controllers/tooltip_controller.js +0 -33
- data/lib/wilday_ui/version.rb +1 -1
- metadata +12 -3
- data/app/helpers/wilday_ui/components/button_helper.rb +0 -672
@@ -0,0 +1,74 @@
|
|
1
|
+
module WildayUi
|
2
|
+
module Components
|
3
|
+
module Button
|
4
|
+
module FeatureEngine
|
5
|
+
include WildayUi::Components::Button::Features::Loading
|
6
|
+
include WildayUi::Components::Button::Features::Dropdown
|
7
|
+
include WildayUi::Components::Button::Features::CopyToClipboard
|
8
|
+
include WildayUi::Components::Button::Features::ConfirmDialog
|
9
|
+
include WildayUi::Components::Button::Features::Tooltip
|
10
|
+
include WildayUi::Components::Button::Features::Animation
|
11
|
+
|
12
|
+
BUTTON_FEATURES = begin
|
13
|
+
{}
|
14
|
+
.merge(Features::Loading.feature_config)
|
15
|
+
.merge(Features::Tooltip.feature_config)
|
16
|
+
.merge(Features::Dropdown.feature_config)
|
17
|
+
.merge(Features::CopyToClipboard.feature_config)
|
18
|
+
.merge(Features::ConfirmDialog.feature_config)
|
19
|
+
.merge(Features::Animation.feature_config)
|
20
|
+
.freeze
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.button_features
|
24
|
+
BUTTON_FEATURES
|
25
|
+
end
|
26
|
+
|
27
|
+
def determine_active_features(loading, dropdown, loading_text = nil, copy_to_clipboard = nil, confirm = nil, tooltip = nil, animation = nil, use_default_controller = true)
|
28
|
+
features = {}
|
29
|
+
features[:loading] = true if (loading || loading_text.present?) && use_default_controller
|
30
|
+
features[:dropdown] = true if dropdown && use_default_controller
|
31
|
+
features[:copy_to_clipboard] = true if copy_to_clipboard.present? && use_default_controller
|
32
|
+
features[:confirm] = true if confirm.present? && use_default_controller
|
33
|
+
features[:tooltip] = true if tooltip.present? && use_default_controller
|
34
|
+
features[:animation] = true if animation.present? && use_default_controller
|
35
|
+
features
|
36
|
+
end
|
37
|
+
|
38
|
+
def setup_features(active_features, options, use_default_controller)
|
39
|
+
return unless use_default_controller && active_features.any?
|
40
|
+
|
41
|
+
active_features.each do |feature, _value|
|
42
|
+
feature_config = BUTTON_FEATURES[feature]
|
43
|
+
next unless feature_config
|
44
|
+
|
45
|
+
# Skip adding controller for dropdown feature since it's handled by wrapper
|
46
|
+
if feature_config[:wrapper_required]
|
47
|
+
# For dropdown, only set the action, not the controller
|
48
|
+
options[:data][:action] = feature_config[:default_stimulus_action]
|
49
|
+
else
|
50
|
+
setup_feature_controller(options, feature_config)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def setup_feature_controller(options, feature_config)
|
56
|
+
options[:data] ||= {}
|
57
|
+
|
58
|
+
existing_controller = options.dig(:data, :controller)
|
59
|
+
existing_action = options.dig(:data, :action)
|
60
|
+
|
61
|
+
options[:data][:controller] = [
|
62
|
+
existing_controller,
|
63
|
+
feature_config[:stimulus_controller]
|
64
|
+
].compact.join(" ")
|
65
|
+
|
66
|
+
options[:data][:action] = [
|
67
|
+
existing_action,
|
68
|
+
feature_config[:default_stimulus_action]
|
69
|
+
].compact.join(" ")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module WildayUi
|
2
|
+
module Components
|
3
|
+
module Button
|
4
|
+
module Features
|
5
|
+
module Animation
|
6
|
+
FEATURE_CONFIG = {
|
7
|
+
wrapper_required: false,
|
8
|
+
stimulus_controller: "animation",
|
9
|
+
default_stimulus_action: "animation#animate animation#disableAfterAnimation"
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
def self.feature_config
|
13
|
+
{ animation: FEATURE_CONFIG }
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup_animation_options(options, additional_classes, animation, wrapper_data)
|
17
|
+
return unless animation.present?
|
18
|
+
|
19
|
+
animation_config = normalize_animation_options(animation)
|
20
|
+
|
21
|
+
# Add animation data attributes
|
22
|
+
options[:data] ||= {}
|
23
|
+
options[:data][:animation] = animation_config.to_json
|
24
|
+
options[:data][:controller] = "animation"
|
25
|
+
options[:data][:animation_target] = "button"
|
26
|
+
options[:data][:action] = FEATURE_CONFIG[:default_stimulus_action]
|
27
|
+
|
28
|
+
additional_classes = [
|
29
|
+
additional_classes,
|
30
|
+
"w-button-animated",
|
31
|
+
"w-button-animation-#{animation_config[:name]}",
|
32
|
+
"w-button-animation-trigger-#{animation_config[:trigger]}"
|
33
|
+
].compact.join(" ")
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def normalize_animation_options(options)
|
39
|
+
if options.is_a?(Symbol)
|
40
|
+
{
|
41
|
+
name: options,
|
42
|
+
trigger: :hover,
|
43
|
+
timing: :ease,
|
44
|
+
duration: 0.3,
|
45
|
+
iteration: 1,
|
46
|
+
direction: :normal,
|
47
|
+
fill_mode: :none,
|
48
|
+
disabled: false
|
49
|
+
}
|
50
|
+
else
|
51
|
+
config = {
|
52
|
+
name: options[:name]&.to_sym,
|
53
|
+
trigger: options[:trigger]&.to_sym || :hover,
|
54
|
+
timing: normalize_timing(options[:timing]),
|
55
|
+
direction: options[:direction]&.to_sym || :normal,
|
56
|
+
iteration: normalize_iteration(options[:iteration]),
|
57
|
+
fill_mode: options[:fill_mode]&.to_sym || :none,
|
58
|
+
duration: options[:duration] || 0.3,
|
59
|
+
delay: options[:delay] || 0,
|
60
|
+
disabled: options[:disabled] || false
|
61
|
+
}
|
62
|
+
|
63
|
+
# Handle properties based on animation type
|
64
|
+
if options[:properties]
|
65
|
+
if config[:name] == :custom
|
66
|
+
config[:properties] = options[:properties] # Pass through all properties for custom animations
|
67
|
+
elsif config[:timing] == :cubic_bezier
|
68
|
+
config[:properties] = { cubic_bezier: options[:properties][:cubic_bezier] } # Handle cubic-bezier timing
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
config.compact
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def normalize_timing(timing)
|
77
|
+
return :ease unless timing
|
78
|
+
|
79
|
+
timing = timing.to_sym if timing.is_a?(String)
|
80
|
+
case timing
|
81
|
+
when :linear, :ease, :ease_in, :ease_out, :ease_in_out, :cubic_bezier
|
82
|
+
timing
|
83
|
+
else
|
84
|
+
:ease # default if invalid timing provided
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def normalize_iteration(value)
|
89
|
+
return value if value.is_a?(Integer)
|
90
|
+
return :infinite if value.to_s.downcase == "infinite"
|
91
|
+
value&.to_sym
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module WildayUi
|
2
|
+
module Components
|
3
|
+
module Button
|
4
|
+
module Features
|
5
|
+
module ConfirmDialog
|
6
|
+
FEATURE_CONFIG = {
|
7
|
+
wrapper_required: true,
|
8
|
+
stimulus_controller: "confirmation",
|
9
|
+
default_stimulus_action: "click->confirmation#showDialog"
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
def self.feature_config
|
13
|
+
{ confirm: FEATURE_CONFIG }
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup_confirmation_options(options, additional_classes, confirm, wrapper_data)
|
17
|
+
return unless confirm.present?
|
18
|
+
|
19
|
+
confirm_config = normalize_confirmation_options(confirm)
|
20
|
+
|
21
|
+
# Use the same theme processing as regular buttons
|
22
|
+
confirm_theme_styles = process_theme(:solid, { name: confirm_config[:variant] })
|
23
|
+
cancel_theme_styles = process_theme(:subtle, { name: :secondary })
|
24
|
+
|
25
|
+
wrapper_data.merge!(
|
26
|
+
controller: FEATURE_CONFIG[:stimulus_controller],
|
27
|
+
confirmation_title_value: confirm_config[:title],
|
28
|
+
confirmation_message_value: confirm_config[:message],
|
29
|
+
confirmation_icon_color_value: confirm_config[:variant],
|
30
|
+
confirmation_confirm_text_value: confirm_config[:confirm_text],
|
31
|
+
confirmation_cancel_text_value: confirm_config[:cancel_text],
|
32
|
+
confirmation_confirm_styles_value: confirm_theme_styles,
|
33
|
+
confirmation_cancel_styles_value: cancel_theme_styles
|
34
|
+
)
|
35
|
+
|
36
|
+
# Only add loading state if enabled
|
37
|
+
if confirm_config[:loading]
|
38
|
+
wrapper_data.merge!(
|
39
|
+
confirmation_loading_value: "true",
|
40
|
+
confirmation_loading_text_value: confirm_config[:loading_text]
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def normalize_confirmation_options(options)
|
46
|
+
if options.is_a?(String)
|
47
|
+
{
|
48
|
+
title: "Confirm Action",
|
49
|
+
message: options,
|
50
|
+
variant: :info,
|
51
|
+
confirm_text: "Confirm",
|
52
|
+
cancel_text: "Cancel"
|
53
|
+
}
|
54
|
+
else
|
55
|
+
{
|
56
|
+
title: options[:title] || "Confirm Action",
|
57
|
+
message: options[:message],
|
58
|
+
variant: options[:variant] || :info,
|
59
|
+
confirm_text: options[:confirm_text] || "Confirm",
|
60
|
+
cancel_text: options[:cancel_text] || "Cancel",
|
61
|
+
loading: options[:loading] || false,
|
62
|
+
loading_text: options[:loading_text] || "Processing..."
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module WildayUi
|
2
|
+
module Components
|
3
|
+
module Button
|
4
|
+
module Features
|
5
|
+
module CopyToClipboard
|
6
|
+
FEATURE_CONFIG = {
|
7
|
+
wrapper_required: true,
|
8
|
+
stimulus_controller: "clipboard button",
|
9
|
+
default_stimulus_action: "click->clipboard#copy click->button#toggleLoading"
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
def self.feature_config
|
13
|
+
{ copy_to_clipboard: FEATURE_CONFIG }
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup_clipboard_options(options, additional_classes, copy_to_clipboard, wrapper_data)
|
17
|
+
return unless copy_to_clipboard.present?
|
18
|
+
|
19
|
+
clipboard_config = normalize_clipboard_options(copy_to_clipboard)
|
20
|
+
|
21
|
+
wrapper_data.merge!(
|
22
|
+
controller: FEATURE_CONFIG[:stimulus_controller],
|
23
|
+
clipboard_text_value: clipboard_config[:text],
|
24
|
+
clipboard_feedback_text_value: clipboard_config[:feedback_text],
|
25
|
+
clipboard_feedback_position_value: clipboard_config[:position],
|
26
|
+
clipboard_feedback_duration_value: clipboard_config[:duration]
|
27
|
+
)
|
28
|
+
|
29
|
+
options[:data][:clipboard_target] = "button"
|
30
|
+
options[:data][:button_target] = "button"
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def normalize_clipboard_options(options)
|
36
|
+
if options.is_a?(Hash)
|
37
|
+
{
|
38
|
+
text: options[:text],
|
39
|
+
feedback_text: options[:feedback_text] || "Copied!",
|
40
|
+
position: options[:position] || "top",
|
41
|
+
duration: options[:duration] || 2000
|
42
|
+
}
|
43
|
+
else
|
44
|
+
{
|
45
|
+
text: options.to_s,
|
46
|
+
feedback_text: "Copied!",
|
47
|
+
position: "top",
|
48
|
+
duration: 2000
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module WildayUi
|
2
|
+
module Components
|
3
|
+
module Button
|
4
|
+
module Features
|
5
|
+
module Dropdown
|
6
|
+
FEATURE_CONFIG = {
|
7
|
+
wrapper_required: true,
|
8
|
+
stimulus_controller: "dropdown",
|
9
|
+
default_stimulus_action: "click->dropdown#toggle"
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
def self.feature_config
|
13
|
+
{ dropdown: FEATURE_CONFIG }
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup_dropdown_options(options, additional_classes, dropdown, dropdown_items, wrapper_data)
|
17
|
+
additional_classes = "#{additional_classes} w-button-dropdown"
|
18
|
+
|
19
|
+
options[:data][:dropdown_target] = "button"
|
20
|
+
|
21
|
+
wrapper_data.merge!(
|
22
|
+
controller: FEATURE_CONFIG[:stimulus_controller],
|
23
|
+
dropdown_id: "dropdown-#{SecureRandom.hex(4)}"
|
24
|
+
)
|
25
|
+
|
26
|
+
if dropdown.is_a?(Hash)
|
27
|
+
wrapper_data.merge!(
|
28
|
+
dropdown_position_value: dropdown[:position]&.to_s || "bottom",
|
29
|
+
dropdown_align_value: dropdown[:align]&.to_s || "start",
|
30
|
+
dropdown_trigger_value: dropdown[:trigger]&.to_s || "click"
|
31
|
+
)
|
32
|
+
else
|
33
|
+
wrapper_data.merge!(
|
34
|
+
dropdown_position_value: "bottom",
|
35
|
+
dropdown_align_value: "start",
|
36
|
+
dropdown_trigger_value: "click"
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
normalize_dropdown_items(dropdown_items)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def normalize_dropdown_items(items, parent_id = nil)
|
46
|
+
return [] unless items
|
47
|
+
|
48
|
+
items.map.with_index do |item, index|
|
49
|
+
item_id = generate_item_id(parent_id, index)
|
50
|
+
|
51
|
+
normalized_item = {
|
52
|
+
id: item_id,
|
53
|
+
text: item[:text],
|
54
|
+
href: item[:href],
|
55
|
+
divider: item[:divider]
|
56
|
+
}
|
57
|
+
|
58
|
+
if item[:children].present?
|
59
|
+
normalized_item[:children] = normalize_dropdown_items(item[:children], item_id)
|
60
|
+
end
|
61
|
+
|
62
|
+
normalized_item.compact
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def generate_item_id(parent_id, index)
|
67
|
+
base = parent_id ? "#{parent_id}-" : "dropdown-item-"
|
68
|
+
"#{base}#{index}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module WildayUi
|
2
|
+
module Components
|
3
|
+
module Button
|
4
|
+
module Features
|
5
|
+
module Loading
|
6
|
+
FEATURE_CONFIG = {
|
7
|
+
wrapper_required: false,
|
8
|
+
stimulus_controller: "button",
|
9
|
+
default_stimulus_action: "click->button#toggleLoading"
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
def self.feature_config
|
13
|
+
{ loading: FEATURE_CONFIG }
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup_loading_options(options, loading_text)
|
17
|
+
return unless loading_text.present?
|
18
|
+
|
19
|
+
feature_config = FEATURE_CONFIG
|
20
|
+
setup_feature_controller(options, feature_config, loading_text)
|
21
|
+
end
|
22
|
+
|
23
|
+
def setup_loading_data_attributes(options, loading_text)
|
24
|
+
return unless loading_text.present?
|
25
|
+
options[:data] ||= {}
|
26
|
+
options[:data][:button_loading_text] = loading_text
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module WildayUi
|
2
|
+
module Components
|
3
|
+
module Button
|
4
|
+
module Features
|
5
|
+
module Tooltip
|
6
|
+
FEATURE_CONFIG = {
|
7
|
+
wrapper_required: true,
|
8
|
+
stimulus_controller: "tooltip",
|
9
|
+
default_stimulus_action: {
|
10
|
+
hover: "mouseenter->tooltip#show mouseleave->tooltip#hide focusin->tooltip#show focusout->tooltip#hide",
|
11
|
+
click: "click->tooltip#toggle"
|
12
|
+
}
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def self.feature_config
|
16
|
+
{ tooltip: FEATURE_CONFIG }
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup_tooltip_options(options, additional_classes, tooltip, wrapper_data)
|
20
|
+
tooltip_config = normalize_tooltip_options(tooltip)
|
21
|
+
|
22
|
+
# Check if dropdown is present
|
23
|
+
has_dropdown = wrapper_data[:controller]&.include?("dropdown")
|
24
|
+
has_clipboard = wrapper_data[:controller]&.include?("clipboard")
|
25
|
+
|
26
|
+
# Get the appropriate action based on trigger type
|
27
|
+
# Force hover behavior if dropdown is present
|
28
|
+
trigger_type = (has_dropdown || has_clipboard) ? :hover : tooltip_config[:trigger].to_sym
|
29
|
+
tooltip_action = FEATURE_CONFIG[:default_stimulus_action][trigger_type]
|
30
|
+
|
31
|
+
# Merge controllers
|
32
|
+
existing_controller = wrapper_data[:controller]
|
33
|
+
wrapper_data[:controller] = [
|
34
|
+
existing_controller,
|
35
|
+
FEATURE_CONFIG[:stimulus_controller]
|
36
|
+
].compact.join(" ")
|
37
|
+
|
38
|
+
# Merge actions
|
39
|
+
existing_action = wrapper_data[:action]
|
40
|
+
if has_dropdown
|
41
|
+
# Keep the dropdown toggle action and add tooltip hover actions
|
42
|
+
wrapper_data[:action] = [
|
43
|
+
"click->dropdown#toggle", # Ensure dropdown action comes first
|
44
|
+
tooltip_action
|
45
|
+
].compact.join(" ")
|
46
|
+
elsif has_clipboard
|
47
|
+
# Keep the clipboard copy action and add tooltip hover actions
|
48
|
+
wrapper_data[:action] = [
|
49
|
+
"click->clipboard#copy click->button#toggleLoading",
|
50
|
+
tooltip_action
|
51
|
+
].compact.join(" ")
|
52
|
+
else
|
53
|
+
wrapper_data[:action] = [
|
54
|
+
existing_action,
|
55
|
+
tooltip_action
|
56
|
+
].compact.join(" ")
|
57
|
+
end
|
58
|
+
|
59
|
+
# Handle theme data
|
60
|
+
theme = tooltip_config[:theme]
|
61
|
+
theme_name = theme.is_a?(Hash) ? theme[:name] : theme
|
62
|
+
|
63
|
+
wrapper_data.merge!(
|
64
|
+
tooltip_content_value: tooltip_config[:content],
|
65
|
+
tooltip_position_value: tooltip_config[:position],
|
66
|
+
tooltip_align_value: tooltip_config[:align],
|
67
|
+
tooltip_trigger_value: trigger_type,
|
68
|
+
tooltip_show_delay_value: tooltip_config[:delay][:show],
|
69
|
+
tooltip_hide_delay_value: tooltip_config[:delay][:hide],
|
70
|
+
tooltip_offset_value: tooltip_config[:offset],
|
71
|
+
tooltip_theme_value: theme_name,
|
72
|
+
tooltip_size_value: tooltip_config[:size],
|
73
|
+
tooltip_arrow_value: tooltip_config[:arrow]
|
74
|
+
)
|
75
|
+
|
76
|
+
# Add custom theme styles if present
|
77
|
+
if theme.is_a?(Hash) && theme[:custom]
|
78
|
+
custom_styles = []
|
79
|
+
custom_styles << "--tooltip-text-color: #{theme[:custom][:color]}" if theme[:custom][:color]
|
80
|
+
custom_styles << "--tooltip-bg-color: #{theme[:custom][:background]}" if theme[:custom][:background]
|
81
|
+
wrapper_data[:tooltip_custom_style_value] = custom_styles.join(";")
|
82
|
+
end
|
83
|
+
|
84
|
+
options[:data][:tooltip_target] = "trigger"
|
85
|
+
options[:aria] ||= {}
|
86
|
+
options[:aria][:describedby] = "tooltip-#{SecureRandom.hex(4)}"
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def normalize_tooltip_options(options)
|
92
|
+
if options.is_a?(String)
|
93
|
+
{
|
94
|
+
content: options,
|
95
|
+
position: "top",
|
96
|
+
align: "center",
|
97
|
+
trigger: "hover",
|
98
|
+
delay: { show: 0, hide: 0 },
|
99
|
+
offset: 8,
|
100
|
+
theme: "light",
|
101
|
+
size: "md",
|
102
|
+
arrow: false
|
103
|
+
}
|
104
|
+
else
|
105
|
+
theme = options[:theme]
|
106
|
+
theme_data = if theme.is_a?(Hash) && theme[:custom]
|
107
|
+
{
|
108
|
+
name: "custom",
|
109
|
+
custom: {
|
110
|
+
color: theme.dig(:custom, :color),
|
111
|
+
background: theme.dig(:custom, :background)
|
112
|
+
}
|
113
|
+
}
|
114
|
+
else
|
115
|
+
{ name: theme || "light" }
|
116
|
+
end
|
117
|
+
|
118
|
+
{
|
119
|
+
content: options[:content],
|
120
|
+
position: options[:position] || "top",
|
121
|
+
align: options[:align] || "center",
|
122
|
+
trigger: options[:trigger] || "hover",
|
123
|
+
delay: {
|
124
|
+
show: options.dig(:delay, :show) || 0,
|
125
|
+
hide: options.dig(:delay, :hide) || 0
|
126
|
+
},
|
127
|
+
offset: options[:offset] || 8,
|
128
|
+
theme: theme_data,
|
129
|
+
size: options[:size] || "md",
|
130
|
+
arrow: options[:arrow] || false
|
131
|
+
}
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
connect() {
|
5
|
+
console.log("AnimationController connected");
|
6
|
+
this.setupAnimation();
|
7
|
+
}
|
8
|
+
|
9
|
+
setupAnimation() {
|
10
|
+
const animationData = JSON.parse(this.element.dataset.animation || "{}");
|
11
|
+
if (!animationData.name) return;
|
12
|
+
|
13
|
+
const {
|
14
|
+
name,
|
15
|
+
trigger,
|
16
|
+
duration = 0.3,
|
17
|
+
timing = "ease",
|
18
|
+
delay = 0, // Add default value here
|
19
|
+
iteration = 1,
|
20
|
+
direction = "normal",
|
21
|
+
fill_mode = "none",
|
22
|
+
properties,
|
23
|
+
} = animationData;
|
24
|
+
|
25
|
+
// Set up CSS custom properties
|
26
|
+
this.element.style.setProperty("--animation-name", name);
|
27
|
+
this.element.style.setProperty("--animation-duration", `${duration}s`);
|
28
|
+
this.element.style.setProperty("--animation-delay", `${delay}s`); // This will now be "0s" instead of "undefineds"
|
29
|
+
this.element.style.setProperty(
|
30
|
+
"--animation-timing",
|
31
|
+
this.getTimingFunction(timing, properties)
|
32
|
+
);
|
33
|
+
this.element.style.setProperty(
|
34
|
+
"--animation-iteration",
|
35
|
+
iteration === "infinite" ? "infinite" : iteration || 1
|
36
|
+
);
|
37
|
+
this.element.style.setProperty(
|
38
|
+
"--animation-direction",
|
39
|
+
direction.replace(/_/g, "-")
|
40
|
+
);
|
41
|
+
this.element.style.setProperty(
|
42
|
+
"--animation-fill-mode",
|
43
|
+
fill_mode.replace(/_/g, "-")
|
44
|
+
);
|
45
|
+
|
46
|
+
// Handle custom properties
|
47
|
+
if (properties && name === "custom") {
|
48
|
+
Object.entries(properties).forEach(([key, value]) => {
|
49
|
+
this.element.style.setProperty(`--animation-custom-${key}`, value);
|
50
|
+
});
|
51
|
+
}
|
52
|
+
|
53
|
+
if (trigger === "click") {
|
54
|
+
this.element.addEventListener("click", () => this.animate());
|
55
|
+
} else if (trigger === "load") {
|
56
|
+
this.animate();
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
animate() {
|
61
|
+
const animationData = JSON.parse(this.element.dataset.animation || "{}");
|
62
|
+
if (!animationData.name) return;
|
63
|
+
|
64
|
+
// Remove existing animation class to allow re-triggering
|
65
|
+
this.element.classList.remove("is-animating");
|
66
|
+
// Force a reflow to ensure the animation runs again
|
67
|
+
void this.element.offsetWidth;
|
68
|
+
// Add animation class
|
69
|
+
this.element.classList.add("is-animating");
|
70
|
+
}
|
71
|
+
|
72
|
+
getTimingFunction(timing, properties) {
|
73
|
+
if (!timing) return "ease";
|
74
|
+
|
75
|
+
if (timing === "cubic_bezier" && properties?.cubic_bezier) {
|
76
|
+
const [x1, y1, x2, y2] = properties.cubic_bezier;
|
77
|
+
return `cubic-bezier(${x1}, ${y1}, ${x2}, ${y2})`;
|
78
|
+
}
|
79
|
+
return timing.replace(/_/g, "-");
|
80
|
+
}
|
81
|
+
|
82
|
+
disableAfterAnimation() {
|
83
|
+
const animationConfig = JSON.parse(this.element.dataset.animation || "{}");
|
84
|
+
if (!animationConfig.disabled) return; // Only disable if config says so
|
85
|
+
|
86
|
+
const duration =
|
87
|
+
parseFloat(this.element.style.getPropertyValue("--animation-duration")) *
|
88
|
+
1000;
|
89
|
+
const delay =
|
90
|
+
parseFloat(this.element.style.getPropertyValue("--animation-delay")) *
|
91
|
+
1000;
|
92
|
+
|
93
|
+
setTimeout(() => {
|
94
|
+
this.element.disabled = true;
|
95
|
+
}, duration + delay);
|
96
|
+
}
|
97
|
+
}
|
@@ -4,6 +4,7 @@ import DropdownController from "./dropdown_controller";
|
|
4
4
|
import ClipboardController from "./clipboard_controller";
|
5
5
|
import ConfirmationController from "./confirmation_controller";
|
6
6
|
import TooltipController from "./tooltip_controller";
|
7
|
+
import AnimationController from "./animation_controller";
|
7
8
|
// Initialize Stimulus
|
8
9
|
const application = Application.start();
|
9
10
|
window.Stimulus = application;
|
@@ -14,6 +15,7 @@ application.register("dropdown", DropdownController);
|
|
14
15
|
application.register("clipboard", ClipboardController);
|
15
16
|
application.register("confirmation", ConfirmationController);
|
16
17
|
application.register("tooltip", TooltipController);
|
18
|
+
application.register("animation", AnimationController);
|
17
19
|
// Debug check to ensure Stimulus is loaded
|
18
20
|
// if (window.Stimulus) {
|
19
21
|
// console.log("✅ Stimulus is loaded and initialized.");
|