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.
@@ -7,6 +7,10 @@
7
7
  font-size: 1rem;
8
8
  cursor: pointer;
9
9
  transition: all 0.2s ease-in-out;
10
+ white-space: nowrap;
11
+ max-width: 100%;
12
+ overflow: hidden;
13
+ text-overflow: ellipsis;
10
14
  }
11
15
 
12
16
  .w-button:disabled {
@@ -14,3 +18,11 @@
14
18
  color: #6c757d;
15
19
  cursor: not-allowed;
16
20
  }
21
+
22
+ /* Mobile base styles */
23
+ @media screen and (max-width: 767px) {
24
+ .w-button {
25
+ white-space: normal;
26
+ min-height: 40px; /* Touch-friendly */
27
+ }
28
+ }
@@ -0,0 +1,284 @@
1
+ .w-button-animated {
2
+ animation-duration: var(--animation-duration);
3
+ animation-delay: var(--animation-delay);
4
+ animation-timing-function: var(--animation-timing);
5
+ animation-iteration-count: var(--animation-iteration);
6
+ animation-direction: var(--animation-direction);
7
+ animation-fill-mode: var(--animation-fill-mode);
8
+ }
9
+
10
+ /* Hover triggered animations */
11
+ .w-button-animation-trigger-hover:hover {
12
+ animation-name: var(--animation-name);
13
+ }
14
+
15
+ /* Click triggered animations */
16
+ .w-button-animation-trigger-click.is-animating {
17
+ animation-name: var(--animation-name);
18
+ }
19
+
20
+ /* Load triggered animations */
21
+ .w-button-animation-trigger-load.is-animating {
22
+ animation-name: var(--animation-name);
23
+ }
24
+
25
+ /* Custom animation handler */
26
+ .w-button-animation-custom {
27
+ transition-property: transform, background, color, box-shadow, opacity; /* Add all properties we want to transition */
28
+ transition-duration: var(--animation-custom-duration);
29
+ transition-timing-function: var(--animation-custom-timing);
30
+ transition-delay: var(--animation-custom-delay, 0s);
31
+ }
32
+
33
+ /* Handle hover, click, and load triggers */
34
+ .w-button-animation-trigger-hover:hover.w-button-animation-custom,
35
+ .w-button-animation-trigger-click.is-animating.w-button-animation-custom,
36
+ .w-button-animation-trigger-load.is-animating.w-button-animation-custom {
37
+ transform: var(--animation-custom-transform);
38
+ background: var(--animation-custom-background);
39
+ color: var(--animation-custom-color);
40
+ box-shadow: var(--animation-custom-box-shadow);
41
+ opacity: var(--animation-custom-opacity);
42
+ }
43
+
44
+ /* Animation keyframes */
45
+ @keyframes pulse {
46
+ 0% {
47
+ transform: scale(1);
48
+ }
49
+ 50% {
50
+ transform: scale(1.05);
51
+ }
52
+ 100% {
53
+ transform: scale(1);
54
+ }
55
+ }
56
+
57
+ @keyframes scale {
58
+ 0% {
59
+ transform: scale(1);
60
+ }
61
+ 100% {
62
+ transform: scale(1.1);
63
+ }
64
+ }
65
+
66
+ @keyframes shake {
67
+ 0%,
68
+ 100% {
69
+ transform: translateX(0);
70
+ }
71
+ 25% {
72
+ transform: translateX(-5px);
73
+ }
74
+ 75% {
75
+ transform: translateX(5px);
76
+ }
77
+ }
78
+
79
+ @keyframes bounce {
80
+ 0%,
81
+ 100% {
82
+ transform: translateY(0);
83
+ }
84
+ 50% {
85
+ transform: translateY(-10px);
86
+ }
87
+ }
88
+
89
+ @keyframes fade {
90
+ 0% {
91
+ opacity: 1;
92
+ }
93
+ 100% {
94
+ opacity: 0;
95
+ }
96
+ }
97
+
98
+ @keyframes slide {
99
+ 0% {
100
+ transform: translateX(0);
101
+ }
102
+ 100% {
103
+ transform: translateX(10px);
104
+ }
105
+ }
106
+
107
+ @keyframes ripple {
108
+ 0% {
109
+ box-shadow: 0 0 0 0 rgba(var(--w-button-ripple-color, 0, 0, 0), 0.1);
110
+ }
111
+ 100% {
112
+ box-shadow: 0 0 0 15px rgba(var(--w-button-ripple-color, 0, 0, 0), 0);
113
+ }
114
+ }
115
+
116
+ /* Rotate animation */
117
+ @keyframes rotate {
118
+ 0% {
119
+ transform: rotate(0deg);
120
+ }
121
+ 100% {
122
+ transform: rotate(360deg);
123
+ }
124
+ }
125
+
126
+ /* Swing animation */
127
+ @keyframes swing {
128
+ 20% {
129
+ transform: rotate(15deg);
130
+ }
131
+ 40% {
132
+ transform: rotate(-10deg);
133
+ }
134
+ 60% {
135
+ transform: rotate(5deg);
136
+ }
137
+ 80% {
138
+ transform: rotate(-5deg);
139
+ }
140
+ 100% {
141
+ transform: rotate(0deg);
142
+ }
143
+ }
144
+
145
+ /* Pop animation */
146
+ @keyframes pop {
147
+ 0% {
148
+ transform: scale(1);
149
+ }
150
+ 50% {
151
+ transform: scale(1.2);
152
+ }
153
+ 100% {
154
+ transform: scale(1);
155
+ }
156
+ }
157
+
158
+ /* Jello animation */
159
+ @keyframes jello {
160
+ 0%,
161
+ 100% {
162
+ transform: scale3d(1, 1, 1);
163
+ }
164
+ 30% {
165
+ transform: scale3d(1.25, 0.75, 1);
166
+ }
167
+ 40% {
168
+ transform: scale3d(0.75, 1.25, 1);
169
+ }
170
+ 50% {
171
+ transform: scale3d(1.15, 0.85, 1);
172
+ }
173
+ 65% {
174
+ transform: scale3d(0.95, 1.05, 1);
175
+ }
176
+ 75% {
177
+ transform: scale3d(1.05, 0.95, 1);
178
+ }
179
+ }
180
+
181
+ /* Wobble animation */
182
+ @keyframes wobble {
183
+ 0%,
184
+ 100% {
185
+ transform: translateX(0%);
186
+ }
187
+ 15% {
188
+ transform: translateX(-25%) rotate(-5deg);
189
+ }
190
+ 30% {
191
+ transform: translateX(20%) rotate(3deg);
192
+ }
193
+ 45% {
194
+ transform: translateX(-15%) rotate(-3deg);
195
+ }
196
+ 60% {
197
+ transform: translateX(10%) rotate(2deg);
198
+ }
199
+ 75% {
200
+ transform: translateX(-5%) rotate(-1deg);
201
+ }
202
+ }
203
+
204
+ /* Heartbeat animation */
205
+ @keyframes heartbeat {
206
+ 0% {
207
+ transform: scale(1);
208
+ }
209
+ 14% {
210
+ transform: scale(1.3);
211
+ }
212
+ 28% {
213
+ transform: scale(1);
214
+ }
215
+ 42% {
216
+ transform: scale(1.3);
217
+ }
218
+ 70% {
219
+ transform: scale(1);
220
+ }
221
+ }
222
+
223
+ /* Rubber band animation */
224
+ @keyframes rubberBand {
225
+ 0% {
226
+ transform: scale3d(1, 1, 1);
227
+ }
228
+ 30% {
229
+ transform: scale3d(1.25, 0.75, 1);
230
+ }
231
+ 40% {
232
+ transform: scale3d(0.75, 1.25, 1);
233
+ }
234
+ 50% {
235
+ transform: scale3d(1.15, 0.85, 1);
236
+ }
237
+ 65% {
238
+ transform: scale3d(0.95, 1.05, 1);
239
+ }
240
+ 75% {
241
+ transform: scale3d(1.05, 0.95, 1);
242
+ }
243
+ 100% {
244
+ transform: scale3d(1, 1, 1);
245
+ }
246
+ }
247
+
248
+ /* Flash animation */
249
+ @keyframes flash {
250
+ 0%,
251
+ 50%,
252
+ 100% {
253
+ opacity: 1;
254
+ }
255
+ 25%,
256
+ 75% {
257
+ opacity: 0;
258
+ }
259
+ }
260
+
261
+ /* Tada animation */
262
+ @keyframes tada {
263
+ 0% {
264
+ transform: scale3d(1, 1, 1);
265
+ }
266
+ 10%,
267
+ 20% {
268
+ transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
269
+ }
270
+ 30%,
271
+ 50%,
272
+ 70%,
273
+ 90% {
274
+ transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
275
+ }
276
+ 40%,
277
+ 60%,
278
+ 80% {
279
+ transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
280
+ }
281
+ 100% {
282
+ transform: scale3d(1, 1, 1);
283
+ }
284
+ }
@@ -13,3 +13,39 @@
13
13
  font-size: 1.25rem;
14
14
  padding: 0.75rem 1.5rem;
15
15
  }
16
+
17
+ /* Tablet sizes */
18
+ @media screen and (max-width: 767px) {
19
+ .w-button-small {
20
+ font-size: 0.8125rem;
21
+ padding: 0.3rem 0.6rem;
22
+ }
23
+
24
+ .w-button-medium {
25
+ font-size: 0.9375rem;
26
+ padding: 0.45rem 0.9rem;
27
+ }
28
+
29
+ .w-button-large {
30
+ font-size: 1.125rem;
31
+ padding: 0.6rem 1.2rem;
32
+ }
33
+ }
34
+
35
+ /* Mobile sizes */
36
+ @media screen and (max-width: 479px) {
37
+ .w-button-small {
38
+ font-size: 0.75rem;
39
+ padding: 0.25rem 0.5rem;
40
+ }
41
+
42
+ .w-button-medium {
43
+ font-size: 0.875rem;
44
+ padding: 0.4rem 0.8rem;
45
+ }
46
+
47
+ .w-button-large {
48
+ font-size: 1rem;
49
+ padding: 0.5rem 1rem;
50
+ }
51
+ }
@@ -11,6 +11,7 @@
11
11
  *= require ./features/clipboard
12
12
  *= require ./features/confirmation
13
13
  *= require ./features/tooltip
14
+ *= require ./features/animation
14
15
  *= require ../../tokens/colors
15
16
  */
16
17
 
@@ -1,6 +1,6 @@
1
1
  module WildayUi
2
2
  module ApplicationHelper
3
- include WildayUi::Components::ButtonHelper
3
+ include WildayUi::Components::Button::ButtonHelper
4
4
  include WildayUi::JavascriptHelper
5
5
  end
6
6
  end
@@ -0,0 +1,348 @@
1
+ module WildayUi
2
+ module Components
3
+ module Button
4
+ module ButtonHelper
5
+ include WildayUi::Components::Button::FeatureEngine
6
+
7
+ def w_button(
8
+ content,
9
+ variant: :solid,
10
+ size: :medium,
11
+ radius: :rounded,
12
+ gradient: nil,
13
+ icon: nil,
14
+ icon_position: :left,
15
+ icon_only: false,
16
+ loading: false,
17
+ loading_text: nil,
18
+ disabled: false,
19
+ additional_classes: "",
20
+ use_default_controller: true,
21
+ href: nil,
22
+ method: :get,
23
+ target: nil,
24
+ underline: true,
25
+ dropdown: false,
26
+ dropdown_items: nil,
27
+ dropdown_icon: nil,
28
+ theme: nil,
29
+ copy_to_clipboard: nil,
30
+ confirm: nil,
31
+ tooltip: nil,
32
+ animation: nil,
33
+ **options
34
+ )
35
+ content_for(:head) { stylesheet_link_tag "wilday_ui/components/button/index", media: "all" }
36
+
37
+ options[:data] ||= {}
38
+ wrapper_data = {}
39
+ wrapper_options = nil
40
+
41
+ # Process gradient styles if present
42
+ if gradient.present?
43
+ gradient_styles = process_gradient(gradient)
44
+ options[:style] = [ options[:style], gradient_styles ].compact.join(";") if gradient_styles.present?
45
+ end
46
+
47
+ # Process theme styles
48
+ if theme.present?
49
+ theme_styles = process_theme(variant, theme)
50
+ options[:style] = theme_styles if theme_styles.present?
51
+ end
52
+
53
+ variant_class = get_variant_class(variant)
54
+ size_class = get_size_class(size)
55
+ radius_class = get_radius_class(radius)
56
+ gradient_class = get_gradient_class(gradient)
57
+
58
+ # Setup features that require Stimulus controllers
59
+ active_features = determine_active_features(loading, dropdown, loading_text, copy_to_clipboard, confirm, tooltip, animation, use_default_controller)
60
+
61
+ setup_features(active_features, options, use_default_controller)
62
+ setup_loading_data_attributes(options, loading_text) if active_features[:loading]
63
+
64
+ setup_link_options(options, href, target, method)
65
+
66
+ if dropdown
67
+ setup_dropdown_options(
68
+ options,
69
+ additional_classes,
70
+ dropdown,
71
+ dropdown_items,
72
+ wrapper_data
73
+ )
74
+ end
75
+
76
+ if copy_to_clipboard
77
+ setup_clipboard_options(
78
+ options,
79
+ additional_classes,
80
+ copy_to_clipboard,
81
+ wrapper_data
82
+ )
83
+ end
84
+
85
+ if confirm
86
+ setup_confirmation_options(
87
+ options,
88
+ additional_classes,
89
+ confirm,
90
+ wrapper_data
91
+ )
92
+ end
93
+
94
+ if tooltip
95
+ setup_tooltip_options(
96
+ options,
97
+ additional_classes,
98
+ tooltip,
99
+ wrapper_data
100
+ )
101
+ end
102
+
103
+ if animation
104
+ additional_classes = setup_animation_options(
105
+ options,
106
+ additional_classes,
107
+ animation,
108
+ wrapper_data
109
+ )
110
+ end
111
+
112
+ # Setup wrapper options if any feature requires it
113
+ wrapper_options = setup_wrapper_options(
114
+ active_features,
115
+ additional_classes,
116
+ wrapper_data
117
+ )
118
+
119
+ render_button(
120
+ content,
121
+ variant_class,
122
+ size_class,
123
+ radius_class,
124
+ gradient_class,
125
+ icon,
126
+ icon_position,
127
+ icon_only,
128
+ loading,
129
+ loading_text,
130
+ additional_classes,
131
+ disabled,
132
+ options,
133
+ href,
134
+ underline,
135
+ dropdown,
136
+ dropdown_items,
137
+ dropdown_icon,
138
+ wrapper_options
139
+ )
140
+ end
141
+
142
+ private
143
+
144
+ # def get_variant_class(variant)
145
+ # {
146
+ # primary: "w-button-primary",
147
+ # secondary: "w-button-secondary",
148
+ # outline: "w-button-outline"
149
+ # }[variant] || "w-button-primary"
150
+ # end
151
+
152
+ def get_variant_class(variant)
153
+ {
154
+ solid: "w-button-solid",
155
+ subtle: "w-button-subtle",
156
+ surface: "w-button-surface",
157
+ outline: "w-button-outline",
158
+ ghost: "w-button-ghost",
159
+ plain: "w-button-plain"
160
+ }[variant] || "w-button-solid"
161
+ end
162
+
163
+ def get_size_class(size)
164
+ {
165
+ small: "w-button-small",
166
+ medium: "w-button-medium",
167
+ large: "w-button-large"
168
+ }[size] || "w-button-medium"
169
+ end
170
+
171
+ def get_radius_class(radius)
172
+ {
173
+ rounded: "w-button-rounded",
174
+ pill: "w-button-pill",
175
+ square: "w-button-square"
176
+ }[radius] || "w-button-rounded"
177
+ end
178
+
179
+ def get_gradient_class(gradient)
180
+ return nil unless gradient
181
+
182
+ if gradient.is_a?(Hash) && gradient[:from] && gradient[:to]
183
+ "w-button-gradient-custom"
184
+ else
185
+ "w-button-gradient-#{gradient}"
186
+ end
187
+ end
188
+
189
+ def process_gradient(gradient)
190
+ return nil unless gradient.is_a?(Hash) && gradient[:from] && gradient[:to]
191
+
192
+ if gradient[:via]
193
+ "background: linear-gradient(135deg, #{gradient[:from]}, #{gradient[:via]}, #{gradient[:to]})"
194
+ else
195
+ "background: linear-gradient(135deg, #{gradient[:from]}, #{gradient[:to]})"
196
+ end
197
+ end
198
+
199
+ def process_theme(variant, theme)
200
+ return nil unless theme[:name] || theme[:custom]
201
+
202
+ Rails.logger.debug "[Wilday UI] Processing theme for variant: #{variant}"
203
+ Rails.logger.debug "[Wilday UI] Theme config: #{theme.inspect}"
204
+
205
+ styles = {}
206
+
207
+ if theme[:name]
208
+ theme_colors = get_theme_colors(variant, theme[:name])
209
+ styles.merge!(theme_colors)
210
+ end
211
+
212
+ if theme[:custom]
213
+ custom_styles = process_custom_theme(theme[:custom])
214
+ styles.merge!(custom_styles)
215
+ end
216
+
217
+ generate_styles(styles)
218
+ end
219
+
220
+ def get_theme_colors(variant, theme_name)
221
+ config = WildayUi::Config::Theme.configuration
222
+ # Convert theme_name to string
223
+ return {} unless config&.colors&.[](theme_name.to_s)
224
+
225
+ colors = config.colors[theme_name.to_s]
226
+
227
+ case variant
228
+ when :solid
229
+ {
230
+ "--w-button-color": "#FFFFFF",
231
+ "--w-button-bg": colors["500"],
232
+ "--w-button-hover-bg": colors["600"],
233
+ "--w-button-active-bg": colors["700"]
234
+ }
235
+ when :subtle
236
+ {
237
+ "--w-button-color": colors["700"],
238
+ "--w-button-bg": colors["50"],
239
+ "--w-button-hover-bg": colors["100"],
240
+ "--w-button-hover-color": colors["800"]
241
+ }
242
+ when :surface
243
+ {
244
+ "--w-button-color": colors["700"],
245
+ "--w-button-bg": colors["50"],
246
+ "--w-button-hover-bg": colors["100"],
247
+ "--w-button-border": colors["200"],
248
+ "--w-button-hover-border": colors["300"]
249
+ }
250
+ when :outline
251
+ {
252
+ "--w-button-color": colors["600"],
253
+ "--w-button-border": colors["300"],
254
+ "--w-button-hover-bg": colors["50"],
255
+ "--w-button-hover-border": colors["400"],
256
+ "--w-button-hover-color": colors["700"]
257
+ }
258
+ when :ghost
259
+ {
260
+ "--w-button-color": colors["600"],
261
+ "--w-button-hover-bg": colors["50"],
262
+ "--w-button-hover-color": colors["700"]
263
+ }
264
+ when :plain
265
+ {
266
+ "--w-button-color": colors["600"],
267
+ "--w-button-hover-color": colors["700"],
268
+ "--w-button-active-color": colors["800"]
269
+ }
270
+ else
271
+ {}
272
+ end
273
+ end
274
+
275
+ def process_custom_theme(custom)
276
+ return {} unless custom
277
+
278
+ {
279
+ "--w-button-color": custom[:color],
280
+ "--w-button-bg": custom[:background],
281
+ "--w-button-border": custom[:border],
282
+ "--w-button-hover-color": custom.dig(:hover, :color),
283
+ "--w-button-hover-bg": custom.dig(:hover, :background),
284
+ "--w-button-hover-border": custom.dig(:hover, :border),
285
+ "--w-button-active-color": custom.dig(:active, :color),
286
+ "--w-button-active-bg": custom.dig(:active, :background),
287
+ "--w-button-active-border": custom.dig(:active, :border)
288
+ }.compact
289
+ end
290
+
291
+ def generate_styles(styles)
292
+ styles.map { |k, v| "#{k}: #{v}" }.join(";")
293
+ end
294
+
295
+ def setup_link_options(options, href, target, method)
296
+ return unless href.present?
297
+
298
+ options[:href] = href
299
+ options[:target] = target if target
300
+ options[:data][:method] = method if method != :get
301
+ options[:rel] = "noopener noreferrer" if target == "_blank"
302
+ end
303
+
304
+ def setup_wrapper_options(active_features, additional_classes, wrapper_data)
305
+ return nil unless needs_wrapper?(active_features)
306
+
307
+ {
308
+ class: [ "w-button-wrapper", additional_classes ].compact.join(" "),
309
+ data: wrapper_data,
310
+ role: active_features[:dropdown] ? "menu" : nil
311
+ }.compact
312
+ end
313
+
314
+ def needs_wrapper?(active_features)
315
+ active_features.any? { |feature, _| BUTTON_FEATURES[feature][:wrapper_required] }
316
+ end
317
+
318
+ def render_button(content, variant_class, size_class, radius_class, gradient_class, icon, icon_position, icon_only,
319
+ loading, loading_text, additional_classes, disabled, options, href, underline,
320
+ dropdown, dropdown_items, dropdown_icon, wrapper_options)
321
+
322
+ render partial: "wilday_ui/button",
323
+ locals: {
324
+ content: content,
325
+ variant_class: variant_class,
326
+ size_class: size_class,
327
+ radius_class: radius_class,
328
+ gradient_class: gradient_class,
329
+ icon: icon,
330
+ icon_position: icon_position,
331
+ icon_only: icon_only,
332
+ loading: loading,
333
+ loading_text: loading_text,
334
+ additional_classes: additional_classes,
335
+ disabled: disabled,
336
+ html_options: options,
337
+ href: href,
338
+ underline: underline,
339
+ dropdown: dropdown,
340
+ dropdown_items: dropdown_items,
341
+ dropdown_icon: dropdown_icon,
342
+ wrapper_options: wrapper_options
343
+ }
344
+ end
345
+ end
346
+ end
347
+ end
348
+ end