wilday_ui 0.6.0 → 0.7.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.
@@ -0,0 +1,108 @@
1
+ .w-button-feedback {
2
+ position: absolute;
3
+ padding: 0.5rem 1rem;
4
+ background: rgba(0, 0, 0, 0.8);
5
+ color: white;
6
+ border-radius: 4px;
7
+ font-size: 0.875rem;
8
+ pointer-events: none;
9
+ opacity: 0;
10
+ transition: opacity 0.2s ease;
11
+ z-index: 50;
12
+ white-space: nowrap;
13
+ }
14
+
15
+ /* Basic positions */
16
+ .w-button-feedback-top {
17
+ bottom: 100%;
18
+ left: 50%;
19
+ transform: translateX(-50%);
20
+ margin-bottom: 0.5rem;
21
+ }
22
+
23
+ .w-button-feedback-bottom {
24
+ top: 100%;
25
+ left: 50%;
26
+ transform: translateX(-50%);
27
+ margin-top: 0.5rem;
28
+ }
29
+
30
+ .w-button-feedback-left {
31
+ right: 100%;
32
+ top: 50%;
33
+ transform: translateY(-50%);
34
+ margin-right: 0.5rem;
35
+ }
36
+
37
+ .w-button-feedback-right {
38
+ left: 100%;
39
+ top: 50%;
40
+ transform: translateY(-50%);
41
+ margin-left: 0.5rem;
42
+ }
43
+
44
+ /* Start/End modifiers */
45
+ .w-button-feedback-top.w-button-feedback-start,
46
+ .w-button-feedback-bottom.w-button-feedback-start {
47
+ left: 0;
48
+ transform: translateX(0);
49
+ }
50
+
51
+ .w-button-feedback-top.w-button-feedback-end,
52
+ .w-button-feedback-bottom.w-button-feedback-end {
53
+ left: auto;
54
+ right: 0;
55
+ transform: translateX(0);
56
+ }
57
+
58
+ .w-button-feedback-left.w-button-feedback-start,
59
+ .w-button-feedback-right.w-button-feedback-start {
60
+ top: 0;
61
+ transform: translateY(0);
62
+ }
63
+
64
+ .w-button-feedback-left.w-button-feedback-end,
65
+ .w-button-feedback-right.w-button-feedback-end {
66
+ top: auto;
67
+ bottom: 0;
68
+ transform: translateY(0);
69
+ }
70
+
71
+ /* Show state */
72
+ .w-button-feedback-show {
73
+ opacity: 1;
74
+ }
75
+
76
+ /* Optional: Add arrow indicators */
77
+ .w-button-feedback::before {
78
+ content: "";
79
+ position: absolute;
80
+ width: 8px;
81
+ height: 8px;
82
+ background: inherit;
83
+ transform: rotate(45deg);
84
+ }
85
+
86
+ .w-button-feedback-top::before {
87
+ bottom: -4px;
88
+ left: 50%;
89
+ margin-left: -4px;
90
+ }
91
+
92
+ .w-button-feedback-bottom::before {
93
+ top: -4px;
94
+ left: 50%;
95
+ margin-left: -4px;
96
+ }
97
+
98
+ .w-button-feedback-left::before {
99
+ right: -4px;
100
+ top: 50%;
101
+ margin-top: -4px;
102
+ }
103
+
104
+ .w-button-feedback-right::before {
105
+ left: -4px;
106
+ top: 50%;
107
+ margin-top: -4px;
108
+ }
@@ -0,0 +1,136 @@
1
+ .w-button-confirmation-dialog {
2
+ padding: 0;
3
+ border: none;
4
+ border-radius: 0.5rem;
5
+ box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1);
6
+ max-width: 28rem;
7
+ width: 90vw;
8
+ background: var(--w-color-light-50, #ffffff);
9
+ }
10
+
11
+ .w-button-confirmation-dialog::backdrop {
12
+ background-color: rgb(0 0 0 / 0.4);
13
+ backdrop-filter: blur(4px);
14
+ }
15
+
16
+ .w-button-confirmation-dialog-content {
17
+ padding: 1.5rem;
18
+ }
19
+
20
+ .w-button-confirmation-dialog-icon {
21
+ margin-bottom: 1rem;
22
+ width: 2.5rem;
23
+ height: 2.5rem;
24
+ }
25
+
26
+ .w-button-confirmation-dialog-icon svg {
27
+ width: 100%;
28
+ height: 100%;
29
+ }
30
+
31
+ .w-button-confirmation-dialog-icon.info {
32
+ color: var(--w-color-info-500);
33
+ }
34
+
35
+ .w-button-confirmation-dialog-icon.success {
36
+ color: var(--w-color-success-500);
37
+ }
38
+
39
+ .w-button-confirmation-dialog-icon.warning {
40
+ color: var(--w-color-warning-500);
41
+ }
42
+
43
+ .w-button-confirmation-dialog-icon.danger {
44
+ color: var(--w-color-danger-500);
45
+ }
46
+
47
+ .w-button-confirmation-dialog-title {
48
+ font-size: 1.125rem;
49
+ font-weight: 600;
50
+ color: var(--w-color-dark-800, #1f2937); /* Darker text for better contrast */
51
+ margin-bottom: 0.5rem;
52
+ }
53
+
54
+ .w-button-confirmation-dialog-message {
55
+ color: var(--w-color-dark-600, #4b5563); /* Adjusted for better readability */
56
+ margin-bottom: 1.5rem;
57
+ line-height: 1.5;
58
+ }
59
+
60
+ .w-button-confirmation-dialog-actions {
61
+ display: flex;
62
+ justify-content: flex-end;
63
+ gap: 0.75rem;
64
+ }
65
+
66
+ /* Mobile Responsive Styles */
67
+ @media (max-width: 640px) {
68
+ .w-button-confirmation-dialog {
69
+ width: 95vw; /* Slightly wider on mobile */
70
+ margin: 1rem;
71
+ max-height: 90vh; /* Prevent overflow on small screens */
72
+ overflow-y: auto; /* Allow scrolling if content is too tall */
73
+ position: fixed;
74
+ top: 50%;
75
+ left: 50%;
76
+ transform: translate(-50%, -50%);
77
+ margin: 0;
78
+ }
79
+
80
+ .w-button-confirmation-dialog-content {
81
+ padding: 1rem; /* Slightly smaller padding on mobile */
82
+ }
83
+
84
+ .w-button-confirmation-dialog-actions {
85
+ flex-direction: column-reverse; /* Stack buttons vertically */
86
+ gap: 0.5rem; /* Smaller gap between buttons */
87
+ padding: 1rem; /* Add padding around buttons */
88
+ }
89
+
90
+ .w-button-confirmation-dialog-actions button {
91
+ width: 100%; /* Full width buttons */
92
+ margin: 0; /* Remove any margins */
93
+ }
94
+
95
+ .w-button-confirmation-dialog-icon {
96
+ margin-bottom: 0.75rem; /* Slightly reduced spacing */
97
+ width: 2rem; /* Slightly smaller icon */
98
+ height: 2rem;
99
+ }
100
+
101
+ .w-button-confirmation-dialog-title {
102
+ font-size: 1rem; /* Slightly smaller title */
103
+ }
104
+
105
+ .w-button-confirmation-dialog-message {
106
+ font-size: 0.875rem; /* Slightly smaller message text */
107
+ }
108
+ }
109
+
110
+ /* Handle very small screens */
111
+ @media (max-width: 360px) {
112
+ .w-button-confirmation-dialog {
113
+ width: 98vw;
114
+ margin: 0.5rem;
115
+ }
116
+ }
117
+
118
+ /* Handle landscape orientation */
119
+ @media (max-height: 600px) and (orientation: landscape) {
120
+ .w-button-confirmation-dialog {
121
+ max-height: 95vh;
122
+ display: flex;
123
+ flex-direction: row;
124
+ }
125
+
126
+ .w-button-confirmation-dialog-content {
127
+ flex: 1;
128
+ padding: 1rem;
129
+ }
130
+
131
+ .w-button-confirmation-dialog-actions {
132
+ padding: 1rem;
133
+ flex-direction: column;
134
+ justify-content: center;
135
+ }
136
+ }
@@ -152,6 +152,11 @@ a.w-button.w-button-no-underline:hover {
152
152
  color: #000000;
153
153
  }
154
154
 
155
+ /* Override underline for plain buttons */
156
+ a.w-button-plain.w-button-underline {
157
+ text-decoration: none;
158
+ }
159
+
155
160
  .w-button-plain:focus-visible {
156
161
  outline: 2px solid #1a1a1a;
157
162
  outline-offset: 2px;
@@ -8,6 +8,9 @@
8
8
  *= require ./features/loading
9
9
  *= require ./features/dropdown
10
10
  *= require ./features/gradients
11
+ *= require ./features/clipboard
12
+ *= require ./features/confirmation
13
+ *= require ../../tokens/colors
11
14
  */
12
15
 
13
16
  /* Your direct CSS code can go here */
@@ -0,0 +1,109 @@
1
+ :root {
2
+ /* Primary Colors */
3
+ --w-color-primary-50: #e5f0ff;
4
+ --w-color-primary-100: #cce0ff;
5
+ --w-color-primary-200: #99c2ff;
6
+ --w-color-primary-300: #66a3ff;
7
+ --w-color-primary-400: #3385ff;
8
+ --w-color-primary-500: #0066ff;
9
+ --w-color-primary-600: #0052cc;
10
+ --w-color-primary-700: #003d99;
11
+ --w-color-primary-800: #002966;
12
+ --w-color-primary-900: #001433;
13
+
14
+ /* Secondary Colors */
15
+ --w-color-secondary-50: #f8f9fa;
16
+ --w-color-secondary-100: #e9ecef;
17
+ --w-color-secondary-200: #dee2e6;
18
+ --w-color-secondary-300: #ced4da;
19
+ --w-color-secondary-400: #adb5bd;
20
+ --w-color-secondary-500: #6c757d;
21
+ --w-color-secondary-600: #5a6268;
22
+ --w-color-secondary-700: #495057;
23
+ --w-color-secondary-800: #343a40;
24
+ --w-color-secondary-900: #212529;
25
+
26
+ /* Success Colors */
27
+ --w-color-success-50: #ecfdf5;
28
+ --w-color-success-100: #d1fae5;
29
+ --w-color-success-200: #a7f3d0;
30
+ --w-color-success-300: #6ee7b7;
31
+ --w-color-success-400: #34d399;
32
+ --w-color-success-500: #10b981;
33
+ --w-color-success-600: #059669;
34
+ --w-color-success-700: #047857;
35
+ --w-color-success-800: #065f46;
36
+ --w-color-success-900: #064e3b;
37
+
38
+ /* Danger Colors */
39
+ --w-color-danger-50: #fee2e2;
40
+ --w-color-danger-100: #fecaca;
41
+ --w-color-danger-200: #fca5a5;
42
+ --w-color-danger-300: #f87171;
43
+ --w-color-danger-400: #ef4444;
44
+ --w-color-danger-500: #dc2626;
45
+ --w-color-danger-600: #b91c1c;
46
+ --w-color-danger-700: #991b1b;
47
+ --w-color-danger-800: #7f1d1d;
48
+ --w-color-danger-900: #450a0a;
49
+
50
+ /* Warning Colors */
51
+ --w-color-warning-50: #fffbeb;
52
+ --w-color-warning-100: #fef3c7;
53
+ --w-color-warning-200: #fde68a;
54
+ --w-color-warning-300: #fcd34d;
55
+ --w-color-warning-400: #fbbf24;
56
+ --w-color-warning-500: #f59e0b;
57
+ --w-color-warning-600: #d97706;
58
+ --w-color-warning-700: #b45309;
59
+ --w-color-warning-800: #92400e;
60
+ --w-color-warning-900: #78350f;
61
+
62
+ /* Info Colors */
63
+ --w-color-info-50: #eff6ff;
64
+ --w-color-info-100: #dbeafe;
65
+ --w-color-info-200: #bfdbfe;
66
+ --w-color-info-300: #93c5fd;
67
+ --w-color-info-400: #60a5fa;
68
+ --w-color-info-500: #3b82f6;
69
+ --w-color-info-600: #2563eb;
70
+ --w-color-info-700: #1d4ed8;
71
+ --w-color-info-800: #1e40af;
72
+ --w-color-info-900: #1e3a8a;
73
+
74
+ /* Light Colors */
75
+ --w-color-light-50: #ffffff;
76
+ --w-color-light-100: #f8f9fa;
77
+ --w-color-light-200: #f1f3f5;
78
+ --w-color-light-300: #e9ecef;
79
+ --w-color-light-400: #dee2e6;
80
+ --w-color-light-500: #ced4da;
81
+ --w-color-light-600: #adb5bd;
82
+ --w-color-light-700: #868e96;
83
+ --w-color-light-800: #495057;
84
+ --w-color-light-900: #212529;
85
+
86
+ /* Dark Colors */
87
+ --w-color-dark-50: #f9fafb;
88
+ --w-color-dark-100: #f3f4f6;
89
+ --w-color-dark-200: #e5e7eb;
90
+ --w-color-dark-300: #d1d5db;
91
+ --w-color-dark-400: #1f2937;
92
+ --w-color-dark-500: #111827;
93
+ --w-color-dark-600: #0f172a;
94
+ --w-color-dark-700: #0a0f1a;
95
+ --w-color-dark-800: #060912;
96
+ --w-color-dark-900: #030509;
97
+
98
+ /* Link Colors */
99
+ --w-color-link-50: #eff6ff;
100
+ --w-color-link-100: #dbeafe;
101
+ --w-color-link-200: #bfdbfe;
102
+ --w-color-link-300: #93c5fd;
103
+ --w-color-link-400: #60a5fa;
104
+ --w-color-link-500: #2563eb;
105
+ --w-color-link-600: #1d4ed8;
106
+ --w-color-link-700: #1e40af;
107
+ --w-color-link-800: #1e3a8a;
108
+ --w-color-link-900: #1e3a8a;
109
+ }
@@ -2,5 +2,6 @@ module WildayUi
2
2
  module ApplicationHelper
3
3
  include WildayUi::Components::ButtonHelper
4
4
  include WildayUi::JavascriptHelper
5
+ include WildayUi::ThemeHelper
5
6
  end
6
7
  end
@@ -11,6 +11,16 @@ module WildayUi
11
11
  wrapper_required: false,
12
12
  stimulus_controller: "button",
13
13
  default_stimulus_action: "click->button#toggleLoading"
14
+ },
15
+ copy_to_clipboard: {
16
+ wrapper_required: true,
17
+ stimulus_controller: "clipboard button",
18
+ default_stimulus_action: "click->clipboard#copy click->button#toggleLoading"
19
+ },
20
+ confirm: {
21
+ wrapper_required: true,
22
+ stimulus_controller: "confirmation",
23
+ default_stimulus_action: "click->confirmation#showDialog"
14
24
  }
15
25
  # Add more features here as needed
16
26
  # tooltip: {
@@ -42,6 +52,8 @@ module WildayUi
42
52
  dropdown_items: nil,
43
53
  dropdown_icon: nil,
44
54
  theme: nil,
55
+ copy_to_clipboard: nil,
56
+ confirm: nil,
45
57
  **options
46
58
  )
47
59
  content_for(:head) { stylesheet_link_tag "wilday_ui/components/button/index", media: "all" }
@@ -68,7 +80,7 @@ module WildayUi
68
80
  gradient_class = get_gradient_class(gradient)
69
81
 
70
82
  # Setup features that require Stimulus controllers
71
- active_features = determine_active_features(loading, dropdown, loading_text, use_default_controller)
83
+ active_features = determine_active_features(loading, dropdown, loading_text, copy_to_clipboard, confirm, use_default_controller)
72
84
 
73
85
  setup_features(active_features, options, use_default_controller, loading_text)
74
86
 
@@ -84,6 +96,24 @@ module WildayUi
84
96
  )
85
97
  end
86
98
 
99
+ if copy_to_clipboard
100
+ setup_clipboard_options(
101
+ options,
102
+ additional_classes,
103
+ copy_to_clipboard,
104
+ wrapper_data
105
+ )
106
+ end
107
+
108
+ if confirm
109
+ setup_confirmation_options(
110
+ options,
111
+ additional_classes,
112
+ confirm,
113
+ wrapper_data
114
+ )
115
+ end
116
+
87
117
  # Setup wrapper options if any feature requires it
88
118
  wrapper_options = setup_wrapper_options(
89
119
  active_features,
@@ -267,10 +297,12 @@ module WildayUi
267
297
  styles.map { |k, v| "#{k}: #{v}" }.join(";")
268
298
  end
269
299
 
270
- def determine_active_features(loading, dropdown, loading_text = nil, use_default_controller = true)
300
+ def determine_active_features(loading, dropdown, loading_text = nil, copy_to_clipboard = nil, confirm = nil, use_default_controller = true)
271
301
  features = {}
272
302
  features[:loading] = true if (loading || loading_text.present?) && use_default_controller
273
303
  features[:dropdown] = true if dropdown && use_default_controller
304
+ features[:copy_to_clipboard] = true if copy_to_clipboard.present? && use_default_controller
305
+ features[:confirm] = true if confirm.present? && use_default_controller
274
306
  features
275
307
  end
276
308
 
@@ -389,6 +421,92 @@ module WildayUi
389
421
  "#{base}#{index}"
390
422
  end
391
423
 
424
+ def setup_clipboard_options(options, additional_classes, copy_to_clipboard, wrapper_data)
425
+ return unless copy_to_clipboard.present?
426
+
427
+ clipboard_config = normalize_clipboard_options(copy_to_clipboard)
428
+
429
+ wrapper_data.merge!(
430
+ controller: "clipboard button",
431
+ clipboard_text_value: clipboard_config[:text],
432
+ clipboard_feedback_text_value: clipboard_config[:feedback_text],
433
+ clipboard_feedback_position_value: clipboard_config[:position],
434
+ clipboard_feedback_duration_value: clipboard_config[:duration]
435
+ )
436
+
437
+ options[:data][:clipboard_target] = "button"
438
+ options[:data][:button_target] = "button"
439
+ end
440
+
441
+ def normalize_clipboard_options(options)
442
+ if options.is_a?(Hash)
443
+ {
444
+ text: options[:text],
445
+ feedback_text: options[:feedback_text] || "Copied!",
446
+ position: options[:position] || "top",
447
+ duration: options[:duration] || 2000
448
+ }
449
+ else
450
+ {
451
+ text: options.to_s,
452
+ feedback_text: "Copied!",
453
+ position: "top",
454
+ duration: 2000
455
+ }
456
+ end
457
+ end
458
+
459
+ def setup_confirmation_options(options, additional_classes, confirm, wrapper_data)
460
+ return unless confirm.present?
461
+
462
+ confirm_config = normalize_confirmation_options(confirm)
463
+
464
+ # Use the same theme processing as regular buttons
465
+ confirm_theme_styles = process_theme(:solid, { name: confirm_config[:variant] })
466
+ cancel_theme_styles = process_theme(:subtle, { name: :secondary })
467
+
468
+ wrapper_data.merge!(
469
+ controller: "confirmation",
470
+ confirmation_title_value: confirm_config[:title],
471
+ confirmation_message_value: confirm_config[:message],
472
+ confirmation_icon_color_value: confirm_config[:variant],
473
+ confirmation_confirm_text_value: confirm_config[:confirm_text],
474
+ confirmation_cancel_text_value: confirm_config[:cancel_text],
475
+ confirmation_confirm_styles_value: confirm_theme_styles,
476
+ confirmation_cancel_styles_value: cancel_theme_styles
477
+ )
478
+
479
+ # Only add loading state if enabled
480
+ if confirm_config[:loading]
481
+ wrapper_data.merge!(
482
+ confirmation_loading_value: "true",
483
+ confirmation_loading_text_value: confirm_config[:loading_text]
484
+ )
485
+ end
486
+ end
487
+
488
+ def normalize_confirmation_options(options)
489
+ if options.is_a?(String)
490
+ {
491
+ title: "Confirm Action",
492
+ message: options,
493
+ variant: :info,
494
+ confirm_text: "Confirm",
495
+ cancel_text: "Cancel"
496
+ }
497
+ else
498
+ {
499
+ title: options[:title] || "Confirm Action",
500
+ message: options[:message],
501
+ variant: options[:variant] || :info,
502
+ confirm_text: options[:confirm_text] || "Confirm",
503
+ cancel_text: options[:cancel_text] || "Cancel",
504
+ loading: options[:loading] || false,
505
+ loading_text: options[:loading_text] || "Processing..."
506
+ }
507
+ end
508
+ end
509
+
392
510
  def render_button(content, variant_class, size_class, radius_class, gradient_class, icon, icon_position, icon_only,
393
511
  loading, loading_text, additional_classes, disabled, options, href, underline,
394
512
  dropdown, dropdown_items, dropdown_icon, wrapper_options)
@@ -0,0 +1,19 @@
1
+ module WildayUi
2
+ module ThemeHelper
3
+ def generate_theme_css_variables
4
+ colors = WildayUi::Config::Theme.configuration.colors
5
+ css_vars = colors.map do |color_name, shades|
6
+ shades.map do |shade, value|
7
+ "--w-color-#{color_name}-#{shade}: #{value};"
8
+ end
9
+ end.flatten.join("\n")
10
+
11
+ "<style>:root { #{css_vars} }</style>".html_safe
12
+ end
13
+
14
+ # Helper to get specific color value
15
+ def theme_color(name, shade = "500")
16
+ WildayUi::Config::Theme.configuration.colors.dig(name.to_s, shade.to_s)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,76 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["button", "feedback"];
5
+ static values = {
6
+ text: String,
7
+ feedbackText: { type: String, default: "Copied!" },
8
+ feedbackPosition: { type: String, default: "top" },
9
+ feedbackDuration: { type: Number, default: 2000 },
10
+ };
11
+
12
+ connect() {
13
+ // Optional: Initialize any necessary setup
14
+ }
15
+
16
+ async copy(event) {
17
+ event.preventDefault();
18
+
19
+ try {
20
+ await navigator.clipboard.writeText(this.textValue);
21
+ this.showFeedback();
22
+ } catch (err) {
23
+ console.error("Failed to copy text:", err);
24
+ // Fallback for older browsers
25
+ this.fallbackCopy();
26
+ }
27
+ }
28
+
29
+ fallbackCopy() {
30
+ const textArea = document.createElement("textarea");
31
+ textArea.value = this.textValue;
32
+ textArea.style.position = "fixed";
33
+ textArea.style.left = "-9999px";
34
+ document.body.appendChild(textArea);
35
+ textArea.select();
36
+
37
+ try {
38
+ document.execCommand("copy");
39
+ this.showFeedback();
40
+ } catch (err) {
41
+ console.error("Fallback: Oops, unable to copy", err);
42
+ }
43
+
44
+ document.body.removeChild(textArea);
45
+ }
46
+
47
+ showFeedback() {
48
+ const feedback = this.hasFeedbackTarget
49
+ ? this.feedbackTarget
50
+ : this.createFeedbackElement();
51
+ feedback.textContent = this.feedbackTextValue;
52
+
53
+ // Remove any existing position classes
54
+ feedback.className = "w-button-feedback";
55
+
56
+ // Add position-specific classes
57
+ const positionClasses = this.feedbackPositionValue.split("-");
58
+ positionClasses.forEach((pos) => {
59
+ feedback.classList.add(`w-button-feedback-${pos}`);
60
+ });
61
+
62
+ feedback.classList.add("w-button-feedback-show");
63
+
64
+ setTimeout(() => {
65
+ feedback.classList.remove("w-button-feedback-show");
66
+ }, this.feedbackDurationValue);
67
+ }
68
+
69
+ createFeedbackElement() {
70
+ const feedback = document.createElement("div");
71
+ feedback.classList.add("w-button-feedback");
72
+ feedback.setAttribute("data-clipboard-target", "feedback");
73
+ this.element.appendChild(feedback);
74
+ return feedback;
75
+ }
76
+ }