odt-ui-rails 2.2.4 → 2.2.5
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 +21 -2
- data/app/assets/stylesheets/odt_ui/styles.css +489 -109
- data/app/assets/stylesheets/odt_ui.css +489 -109
- data/lib/generators/odt/install_generator.rb +2 -3
- data/lib/generators/odt/templates/odt_dropdown_controller.js +137 -0
- data/lib/generators/odt/templates/odt_modal_controller.js +24 -0
- data/lib/generators/odt/templates/odt_toast_controller.js +12 -0
- data/lib/generators/odt/templates/odt_toaster_controller.js +114 -0
- data/lib/generators/odt/templates/odt_ui.rbs +119 -61
- data/lib/odt_ui/engine.rb +0 -1
- data/lib/odt_ui/helper.rb +324 -282
- data/lib/odt_ui/version.rb +1 -1
- data/lib/odt_ui.rb +6 -1
- data/sig/odt_ui.rbs +119 -61
- metadata +3 -4
- data/app/assets/stylesheets/odt_ui/theme.css +0 -126
- data/app/javascript/controllers/odt_modal_controller.js +0 -61
- data/app/javascript/controllers/odt_toast_controller.js +0 -40
data/lib/odt_ui/helper.rb
CHANGED
|
@@ -2,28 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module OdtUi
|
|
4
4
|
module Helper
|
|
5
|
-
|
|
6
|
-
# Helpers for ODT-Lightweight-UI
|
|
7
|
-
# Standalone CSS First with seamless Tailwind CSS v4 support.
|
|
8
|
-
# =========================================================================
|
|
9
|
-
|
|
10
|
-
# Renders an ODT Button component.
|
|
11
|
-
#
|
|
12
|
-
# @param text [String, nil]
|
|
13
|
-
# @param variant [:filled, :frosted, :capsule, :ghost, :light, :outline, :outlined, :subtle]
|
|
14
|
-
# @param color [:primary, :secondary, :surface, :muted, :inverse, :success, :warning, :danger, :info, :neutral]
|
|
15
|
-
# @param size [:xs, :sm, :md, :lg, :xl]
|
|
16
|
-
# @param radius [:none, :xs, :sm, :md, :lg, :xl, :"2xl", :"3xl", :"4xl", :full, nil]
|
|
17
|
-
# @param shadow [:none, :card, :dropdown, :modal, :popover, nil]
|
|
18
|
-
# @param icon_only [Boolean]
|
|
19
|
-
# @param full_width [Boolean]
|
|
20
|
-
# @param loading [Boolean]
|
|
21
|
-
# @param disabled [Boolean]
|
|
22
|
-
# @param href [String, nil]
|
|
23
|
-
# @param icon [String, nil]
|
|
24
|
-
# @param right_icon [String, nil]
|
|
25
|
-
# @param type [String]
|
|
26
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
5
|
+
|
|
27
6
|
def odt_button(text = nil, variant: :filled, color: :primary, size: :md, radius: nil, shadow: nil, icon_only: false, full_width: false, loading: false, disabled: false, href: nil, icon: nil, right_icon: nil, type: "button", **html_options, &block)
|
|
28
7
|
variant_key = normalize_button_variant(variant)
|
|
29
8
|
color_key = color.to_s.tr("_", "-")
|
|
@@ -31,11 +10,10 @@ module OdtUi
|
|
|
31
10
|
|
|
32
11
|
classes = [
|
|
33
12
|
"odt-btn",
|
|
13
|
+
"odt-btn--#{variant_key}",
|
|
34
14
|
"odt-btn--#{variant_key}-#{color_key}",
|
|
35
15
|
"odt-btn--#{size_key}"
|
|
36
16
|
]
|
|
37
|
-
|
|
38
|
-
classes << "odt-btn--capsule" if variant.to_sym == :capsule
|
|
39
17
|
classes << "odt-radius-#{radius.to_s.tr('_', '-')}" if radius.present?
|
|
40
18
|
classes << "odt-shadow-#{shadow.to_s.tr('_', '-')}" if shadow.present?
|
|
41
19
|
classes << "odt-btn--full-width" if full_width
|
|
@@ -74,16 +52,6 @@ module OdtUi
|
|
|
74
52
|
end
|
|
75
53
|
end
|
|
76
54
|
|
|
77
|
-
# Renders an ODT Badge component.
|
|
78
|
-
#
|
|
79
|
-
# @param text [String, nil]
|
|
80
|
-
# @param variant [:subtle, :filled, :outlined, :outline]
|
|
81
|
-
# @param color [:primary, :secondary, :success, :warning, :danger, :info, :neutral]
|
|
82
|
-
# @param size [:sm, :md]
|
|
83
|
-
# @param radius [:none, :xs, :sm, :md, :lg, :xl, :"2xl", :"3xl", :"4xl", :full, nil]
|
|
84
|
-
# @param dot [Boolean]
|
|
85
|
-
# @param icon [String, nil]
|
|
86
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
87
55
|
def odt_badge(text = nil, variant: :subtle, color: :primary, size: :md, radius: nil, dot: false, icon: nil, **html_options, &block)
|
|
88
56
|
variant_key = variant.to_sym == :outline ? "outlined" : variant.to_s.tr("_", "-")
|
|
89
57
|
color_key = color.to_s.tr("_", "-")
|
|
@@ -91,6 +59,7 @@ module OdtUi
|
|
|
91
59
|
|
|
92
60
|
classes = [
|
|
93
61
|
"odt-badge",
|
|
62
|
+
"odt-badge--#{variant_key}",
|
|
94
63
|
"odt-badge--#{variant_key}-#{color_key}",
|
|
95
64
|
"odt-badge--#{size_key}"
|
|
96
65
|
]
|
|
@@ -111,20 +80,6 @@ module OdtUi
|
|
|
111
80
|
tag.span(inner_content, **opts)
|
|
112
81
|
end
|
|
113
82
|
|
|
114
|
-
# Renders an ODT Card container.
|
|
115
|
-
#
|
|
116
|
-
# @param title [String, nil]
|
|
117
|
-
# @param subtitle [String, nil]
|
|
118
|
-
# @param variant [:elevated, :outlined, :subtle, :muted, :ghost, :frosted]
|
|
119
|
-
# @param background [Symbol, String, nil]
|
|
120
|
-
# @param border [Symbol, String, Boolean, nil]
|
|
121
|
-
# @param shadow [Symbol, String, nil]
|
|
122
|
-
# @param radius [Symbol, String, nil]
|
|
123
|
-
# @param padding [Symbol, String, nil]
|
|
124
|
-
# @param hoverable [Boolean]
|
|
125
|
-
# @param hover [Boolean] Alias for hoverable
|
|
126
|
-
# @param as [:div, :article, :section, :aside, :main]
|
|
127
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
128
83
|
def odt_card(title: nil, subtitle: nil, variant: :elevated, background: nil, border: nil, shadow: nil, radius: nil, padding: nil, hoverable: false, hover: false, as: :div, **html_options, &block)
|
|
129
84
|
tag_name = as.to_sym
|
|
130
85
|
variant_key = variant.to_s.tr("_", "-")
|
|
@@ -165,12 +120,6 @@ module OdtUi
|
|
|
165
120
|
tag.public_send(tag_name, inner_content, **opts)
|
|
166
121
|
end
|
|
167
122
|
|
|
168
|
-
# Renders an ODT Alert banner.
|
|
169
|
-
#
|
|
170
|
-
# @param message [String, nil]
|
|
171
|
-
# @param variant [:success, :warning, :danger, :info]
|
|
172
|
-
# @param icon [String, nil]
|
|
173
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
174
123
|
def odt_alert(message = nil, variant: :success, icon: nil, **html_options, &block)
|
|
175
124
|
variant_key = variant.to_sym
|
|
176
125
|
default_icons = {
|
|
@@ -200,15 +149,6 @@ module OdtUi
|
|
|
200
149
|
tag.div(inner_content, **opts)
|
|
201
150
|
end
|
|
202
151
|
|
|
203
|
-
# Renders an ODT Heading element.
|
|
204
|
-
#
|
|
205
|
-
# @param text [String, nil]
|
|
206
|
-
# @param as [:h1, :h2, :h3, :h4, :h5, :h6]
|
|
207
|
-
# @param size [:5xl, :4xl, :3xl, :2xl, :xl, :lg, nil]
|
|
208
|
-
# @param weight [:light, :normal, :medium, :semibold, :bold, :extrabold, nil]
|
|
209
|
-
# @param color [:strong, :default, :muted, :subtle, :disabled, :primary, :secondary, :success, :warning, :danger, :info]
|
|
210
|
-
# @param truncate [Boolean]
|
|
211
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
212
152
|
def odt_heading(text = nil, as: :h2, size: nil, weight: nil, color: :strong, truncate: false, **html_options, &block)
|
|
213
153
|
tag_name = as.to_sym
|
|
214
154
|
default_sizes = {
|
|
@@ -240,15 +180,6 @@ module OdtUi
|
|
|
240
180
|
tag.public_send(tag_name, inner_content, **opts)
|
|
241
181
|
end
|
|
242
182
|
|
|
243
|
-
# Renders styled typography body text.
|
|
244
|
-
#
|
|
245
|
-
# @param text [String, nil]
|
|
246
|
-
# @param as [:p, :span, :div, :label, :small]
|
|
247
|
-
# @param size [:xs, :sm, :base, :lg, :xl, :"2xl"]
|
|
248
|
-
# @param weight [:light, :normal, :medium, :semibold, :bold, :extrabold, nil]
|
|
249
|
-
# @param color [:default, :strong, :muted, :subtle, :disabled, :primary, :secondary, :success, :warning, :danger, :info]
|
|
250
|
-
# @param truncate [Boolean]
|
|
251
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
252
183
|
def odt_text(text = nil, as: :p, size: :base, weight: nil, color: :default, truncate: false, **html_options, &block)
|
|
253
184
|
tag_name = as.to_sym
|
|
254
185
|
resolved_size = size.to_s.tr("_", "-")
|
|
@@ -270,15 +201,6 @@ module OdtUi
|
|
|
270
201
|
tag.public_send(tag_name, inner_content, **opts)
|
|
271
202
|
end
|
|
272
203
|
|
|
273
|
-
# Renders an ODT Avatar element.
|
|
274
|
-
#
|
|
275
|
-
# @param name [String, nil]
|
|
276
|
-
# @param src [String, nil]
|
|
277
|
-
# @param size [:xs, :sm, :md, :lg, :xl, :"2xl"]
|
|
278
|
-
# @param color [:secondary, :primary, :neutral]
|
|
279
|
-
# @param icon [String]
|
|
280
|
-
# @param fallback [String, nil]
|
|
281
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
282
204
|
def odt_avatar(name: nil, src: nil, size: :md, color: :secondary, icon: "fa-solid fa-user", fallback: nil, **html_options)
|
|
283
205
|
size_key = size.to_s.tr("_", "-")
|
|
284
206
|
color_key = color.to_s.tr("_", "-")
|
|
@@ -305,27 +227,6 @@ module OdtUi
|
|
|
305
227
|
tag.div(inner_content, **opts)
|
|
306
228
|
end
|
|
307
229
|
|
|
308
|
-
# Renders an ODT text/number/email/password input.
|
|
309
|
-
#
|
|
310
|
-
# @param label [String, nil]
|
|
311
|
-
# @param variant [:outlined, :filled, :frosted]
|
|
312
|
-
# @param size [:sm, :md, :lg]
|
|
313
|
-
# @param radius [:none, :xs, :sm, :md, :lg, :xl, :"2xl", :"3xl", :"4xl", :full, nil]
|
|
314
|
-
# @param type [String]
|
|
315
|
-
# @param placeholder [String, nil]
|
|
316
|
-
# @param error [String, nil]
|
|
317
|
-
# @param helper [String, nil]
|
|
318
|
-
# @param helper_text [String, nil] Alias for helper
|
|
319
|
-
# @param success [String, nil]
|
|
320
|
-
# @param left_icon [String, nil]
|
|
321
|
-
# @param right_icon [String, nil]
|
|
322
|
-
# @param prefix [String, nil]
|
|
323
|
-
# @param suffix [String, nil]
|
|
324
|
-
# @param addon_before [String, nil]
|
|
325
|
-
# @param addon_after [String, nil]
|
|
326
|
-
# @param disabled [Boolean]
|
|
327
|
-
# @param full_width [Boolean]
|
|
328
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
329
230
|
def odt_input(label: nil, variant: :outlined, size: :md, radius: nil, type: "text", placeholder: nil, error: nil, helper: nil, helper_text: nil, success: nil, left_icon: nil, right_icon: nil, prefix: nil, suffix: nil, addon_before: nil, addon_after: nil, disabled: false, full_width: true, **html_options)
|
|
330
231
|
variant_key = variant.to_s.tr("_", "-")
|
|
331
232
|
size_key = size.to_s.tr("_", "-")
|
|
@@ -405,21 +306,6 @@ module OdtUi
|
|
|
405
306
|
end
|
|
406
307
|
end
|
|
407
308
|
|
|
408
|
-
# Renders an ODT textarea field.
|
|
409
|
-
#
|
|
410
|
-
# @param label [String, nil]
|
|
411
|
-
# @param variant [:outlined, :filled, :frosted]
|
|
412
|
-
# @param size [:sm, :md, :lg]
|
|
413
|
-
# @param radius [:none, :xs, :sm, :md, :lg, :xl, :"2xl", :"3xl", :"4xl", :full, nil]
|
|
414
|
-
# @param rows [Integer]
|
|
415
|
-
# @param resize [:none, :vertical, :horizontal, :both]
|
|
416
|
-
# @param error [String, nil]
|
|
417
|
-
# @param helper [String, nil]
|
|
418
|
-
# @param helper_text [String, nil]
|
|
419
|
-
# @param success [String, nil]
|
|
420
|
-
# @param disabled [Boolean]
|
|
421
|
-
# @param full_width [Boolean]
|
|
422
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
423
309
|
def odt_textarea(label: nil, variant: :outlined, size: :md, radius: nil, rows: 4, resize: :vertical, error: nil, helper: nil, helper_text: nil, success: nil, disabled: false, full_width: true, **html_options, &block)
|
|
424
310
|
variant_key = variant.to_s.tr("_", "-")
|
|
425
311
|
size_key = size.to_s.tr("_", "-")
|
|
@@ -468,81 +354,24 @@ module OdtUi
|
|
|
468
354
|
end
|
|
469
355
|
end
|
|
470
356
|
|
|
471
|
-
# Renders an ODT select dropdown.
|
|
472
|
-
#
|
|
473
|
-
# @param label [String, nil]
|
|
474
|
-
# @param options [Array] Options compatible with Rails options_for_select
|
|
475
|
-
# @param variant [:outlined, :filled, :frosted]
|
|
476
|
-
# @param size [:sm, :md, :lg]
|
|
477
|
-
# @param radius [:none, :xs, :sm, :md, :lg, :xl, :"2xl", :"3xl", :"4xl", :full, nil]
|
|
478
|
-
# @param prompt [String, nil]
|
|
479
|
-
# @param error [String, nil]
|
|
480
|
-
# @param helper [String, nil]
|
|
481
|
-
# @param helper_text [String, nil]
|
|
482
|
-
# @param success [String, nil]
|
|
483
|
-
# @param disabled [Boolean]
|
|
484
|
-
# @param full_width [Boolean]
|
|
485
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
486
357
|
def odt_select(label: nil, options: [], variant: :outlined, size: :md, radius: nil, prompt: nil, error: nil, helper: nil, helper_text: nil, success: nil, disabled: false, full_width: true, **html_options)
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
classes << "odt-select--success" if has_success
|
|
502
|
-
classes << html_options.delete(:class)
|
|
503
|
-
|
|
504
|
-
opts = html_options.merge(
|
|
505
|
-
class: classes.compact.reject(&:blank?).join(" "),
|
|
506
|
-
disabled: disabled
|
|
358
|
+
render_custom_select(
|
|
359
|
+
label: label,
|
|
360
|
+
options: options,
|
|
361
|
+
variant: variant,
|
|
362
|
+
size: size,
|
|
363
|
+
radius: radius,
|
|
364
|
+
prompt: prompt,
|
|
365
|
+
error: error,
|
|
366
|
+
helper: helper,
|
|
367
|
+
helper_text: helper_text,
|
|
368
|
+
success: success,
|
|
369
|
+
disabled: disabled,
|
|
370
|
+
full_width: full_width,
|
|
371
|
+
**html_options
|
|
507
372
|
)
|
|
508
|
-
|
|
509
|
-
option_tags = if prompt.present?
|
|
510
|
-
tag.option(prompt, value: "") + (options.is_a?(String) ? options.html_safe : options_for_select(options))
|
|
511
|
-
else
|
|
512
|
-
options.is_a?(String) ? options.html_safe : options_for_select(options)
|
|
513
|
-
end
|
|
514
|
-
|
|
515
|
-
container_classes = ["odt-input-container"]
|
|
516
|
-
container_classes << "odt-input-container--full-width" if full_width
|
|
517
|
-
|
|
518
|
-
tag.div(class: container_classes.join(" ")) do
|
|
519
|
-
parts = []
|
|
520
|
-
parts << tag.label(label, class: "odt-label") if label.present?
|
|
521
|
-
parts << tag.select(option_tags, **opts)
|
|
522
|
-
|
|
523
|
-
if has_error
|
|
524
|
-
parts << tag.p(error, class: "odt-error-text", role: "alert")
|
|
525
|
-
elsif has_success
|
|
526
|
-
parts << tag.p(success, class: "odt-success-text")
|
|
527
|
-
elsif resolved_helper.present?
|
|
528
|
-
parts << tag.p(resolved_helper, class: "odt-helper-text")
|
|
529
|
-
end
|
|
530
|
-
|
|
531
|
-
safe_join(parts)
|
|
532
|
-
end
|
|
533
373
|
end
|
|
534
374
|
|
|
535
|
-
# Renders an ODT styled Checkbox.
|
|
536
|
-
#
|
|
537
|
-
# @param label [String, nil]
|
|
538
|
-
# @param description [String, nil]
|
|
539
|
-
# @param checked [Boolean]
|
|
540
|
-
# @param color [:primary, :secondary, :success, :warning, :danger, :dark, :neutral]
|
|
541
|
-
# @param size [:sm, :md, :lg]
|
|
542
|
-
# @param radius [:none, :xs, :sm, :md, :lg, :full, nil]
|
|
543
|
-
# @param disabled [Boolean]
|
|
544
|
-
# @param error [String, nil]
|
|
545
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
546
375
|
def odt_checkbox(label: nil, description: nil, checked: false, color: :primary, size: :md, radius: nil, disabled: false, error: nil, **html_options)
|
|
547
376
|
color_key = color.to_sym == :dark ? "neutral" : color.to_s.tr("_", "-")
|
|
548
377
|
size_key = size.to_s.tr("_", "-")
|
|
@@ -587,16 +416,6 @@ module OdtUi
|
|
|
587
416
|
end
|
|
588
417
|
end
|
|
589
418
|
|
|
590
|
-
# Renders an ODT styled Radio button.
|
|
591
|
-
#
|
|
592
|
-
# @param label [String, nil]
|
|
593
|
-
# @param description [String, nil]
|
|
594
|
-
# @param checked [Boolean]
|
|
595
|
-
# @param color [:primary, :secondary, :success, :warning, :danger, :dark, :neutral]
|
|
596
|
-
# @param size [:sm, :md, :lg]
|
|
597
|
-
# @param disabled [Boolean]
|
|
598
|
-
# @param error [String, nil]
|
|
599
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
600
419
|
def odt_radio(label: nil, description: nil, checked: false, color: :primary, size: :md, disabled: false, error: nil, **html_options)
|
|
601
420
|
color_key = color.to_sym == :dark ? "neutral" : color.to_s.tr("_", "-")
|
|
602
421
|
size_key = size.to_s.tr("_", "-")
|
|
@@ -638,15 +457,6 @@ module OdtUi
|
|
|
638
457
|
end
|
|
639
458
|
end
|
|
640
459
|
|
|
641
|
-
# Renders a Form Group wrapper with label, slot, error, and helper texts.
|
|
642
|
-
#
|
|
643
|
-
# @param label [String, nil]
|
|
644
|
-
# @param error [String, nil]
|
|
645
|
-
# @param helper [String, nil]
|
|
646
|
-
# @param helper_text [String, nil]
|
|
647
|
-
# @param success [String, nil]
|
|
648
|
-
# @param required [Boolean]
|
|
649
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
650
460
|
def odt_form_group(label: nil, error: nil, helper: nil, helper_text: nil, success: nil, required: false, **html_options, &block)
|
|
651
461
|
resolved_helper = helper.presence || helper_text.presence
|
|
652
462
|
has_error = error.present?
|
|
@@ -677,12 +487,7 @@ module OdtUi
|
|
|
677
487
|
end
|
|
678
488
|
end
|
|
679
489
|
|
|
680
|
-
|
|
681
|
-
#
|
|
682
|
-
# @param position [:top_right, :top_center, :top_left, :bottom_right, :bottom_center, :bottom_left]
|
|
683
|
-
# @param id [String] Element ID for Turbo Stream targeting (default: "odt-toasts")
|
|
684
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
685
|
-
def odt_toaster(position: :top_right, id: "odt-toasts", **html_options, &block)
|
|
490
|
+
def odt_toaster(position: :top_right, id: "odt-toasts", expand: true, **html_options, &block)
|
|
686
491
|
pos_key = position.to_s.tr("_", "-")
|
|
687
492
|
classes = [
|
|
688
493
|
"odt-toaster",
|
|
@@ -690,44 +495,42 @@ module OdtUi
|
|
|
690
495
|
html_options.delete(:class)
|
|
691
496
|
]
|
|
692
497
|
|
|
498
|
+
data_attrs = {
|
|
499
|
+
controller: "odt-toaster",
|
|
500
|
+
odt_toaster_position_value: pos_key,
|
|
501
|
+
odt_toaster_expand_value: expand,
|
|
502
|
+
action: "mouseenter->odt-toaster#hover mouseleave->odt-toaster#leave"
|
|
503
|
+
}.merge(html_options.delete(:data) || {})
|
|
504
|
+
|
|
693
505
|
opts = html_options.merge(
|
|
694
506
|
id: id,
|
|
695
507
|
class: classes.compact.reject(&:blank?).join(" "),
|
|
696
508
|
role: "region",
|
|
697
|
-
aria: { live: "polite" }
|
|
509
|
+
aria: { live: "polite" },
|
|
510
|
+
data: data_attrs
|
|
698
511
|
)
|
|
699
512
|
|
|
700
513
|
inner_content = block_given? ? capture(&block) : nil
|
|
701
514
|
tag.div(inner_content, **opts)
|
|
702
515
|
end
|
|
703
516
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
def odt_toast(message = nil, title: nil, variant: :success, icon: nil, duration: 4000, dismissible: true, radius: nil, **html_options, &block)
|
|
715
|
-
variant_key = variant.to_sym == :error ? :danger : variant.to_sym
|
|
716
|
-
default_icons = {
|
|
717
|
-
success: "fa-solid fa-circle-check",
|
|
718
|
-
warning: "fa-solid fa-triangle-exclamation",
|
|
719
|
-
danger: "fa-solid fa-circle-xmark",
|
|
720
|
-
info: "fa-solid fa-circle-info",
|
|
721
|
-
loading: "fa-solid fa-spinner"
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
icon_name = icon || default_icons[variant_key] || default_icons[:info]
|
|
517
|
+
def odt_toast(message = nil, title: nil, variant: :elevated, type: :default, icon: nil, duration: 4000, dismissible: true, radius: :"2xl", rich_colors: false, **html_options, &block)
|
|
518
|
+
semantic_types = %i[success warning danger error info loading]
|
|
519
|
+
|
|
520
|
+
if semantic_types.include?(variant.to_sym)
|
|
521
|
+
resolved_type = variant.to_sym == :error ? :danger : variant.to_sym
|
|
522
|
+
resolved_variant = :elevated
|
|
523
|
+
else
|
|
524
|
+
resolved_variant = variant.to_sym
|
|
525
|
+
resolved_type = (type.to_sym == :error ? :danger : type.to_sym)
|
|
526
|
+
end
|
|
725
527
|
|
|
726
528
|
classes = [
|
|
727
529
|
"odt-toast",
|
|
728
|
-
"odt-toast
|
|
530
|
+
"odt-toast--variant-#{resolved_variant.to_s.tr('_', '-')}"
|
|
729
531
|
]
|
|
730
532
|
classes << "odt-radius-#{radius.to_s.tr('_', '-')}" if radius.present?
|
|
533
|
+
classes << "odt-toast--rich-#{resolved_type}" if rich_colors && resolved_type != :default
|
|
731
534
|
classes << html_options.delete(:class)
|
|
732
535
|
|
|
733
536
|
data_attrs = {
|
|
@@ -743,23 +546,54 @@ module OdtUi
|
|
|
743
546
|
)
|
|
744
547
|
|
|
745
548
|
parts = []
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
549
|
+
|
|
550
|
+
if icon.present?
|
|
551
|
+
icon_classes = ["odt-toast__icon", "odt-toast__icon--#{resolved_type}"]
|
|
552
|
+
parts << tag.span(render_icon_element(icon), class: icon_classes.join(" "))
|
|
553
|
+
elsif resolved_type != :default
|
|
554
|
+
icon_classes = ["odt-toast__icon", "odt-toast__icon--#{resolved_type}"]
|
|
555
|
+
svg_icon = case resolved_type
|
|
556
|
+
when :success
|
|
557
|
+
tag.svg(tag.path(d: "M20 6L9 17l-5-5"), width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2.5", "stroke-linecap": "round", "stroke-linejoin": "round")
|
|
558
|
+
when :info
|
|
559
|
+
tag.svg(safe_join([tag.circle(cx: "12", cy: "12", r: "10"), tag.line(x1: "12", y1: "16", x2: "12", y2: "12"), tag.line(x1: "12", y1: "8", x2: "12.01", y2: "8")]), width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2.5", "stroke-linecap": "round", "stroke-linejoin": "round")
|
|
560
|
+
when :warning
|
|
561
|
+
tag.svg(safe_join([tag.path(d: "M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"), tag.line(x1: "12", y1: "9", x2: "12", y2: "13"), tag.line(x1: "12", y1: "17", x2: "12.01", y2: "17")]), width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2.5", "stroke-linecap": "round", "stroke-linejoin": "round")
|
|
562
|
+
when :danger
|
|
563
|
+
tag.svg(safe_join([tag.circle(cx: "12", cy: "12", r: "10"), tag.line(x1: "15", y1: "9", x2: "9", y2: "15"), tag.line(x1: "9", y1: "9", x2: "15", y2: "15")]), width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2.5", "stroke-linecap": "round", "stroke-linejoin": "round")
|
|
564
|
+
when :loading
|
|
565
|
+
tag.svg(tag.path(d: "M21 12a9 9 0 11-6.219-8.56"), width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2.5", "stroke-linecap": "round", "stroke-linejoin": "round")
|
|
566
|
+
end
|
|
567
|
+
parts << tag.span(svg_icon, class: icon_classes.join(" ")) if svg_icon
|
|
749
568
|
end
|
|
750
569
|
|
|
751
570
|
body_content = block_given? ? capture(&block) : message
|
|
752
571
|
|
|
753
572
|
parts << tag.div(class: "odt-toast__content") do
|
|
754
573
|
c_parts = []
|
|
755
|
-
|
|
756
|
-
|
|
574
|
+
if title.present?
|
|
575
|
+
c_parts << tag.p(title, class: "odt-toast__title")
|
|
576
|
+
c_parts << tag.p(body_content, class: "odt-toast__description") if body_content.present?
|
|
577
|
+
elsif body_content.present?
|
|
578
|
+
c_parts << tag.p(body_content, class: "odt-toast__title")
|
|
579
|
+
end
|
|
757
580
|
safe_join(c_parts)
|
|
758
581
|
end
|
|
759
582
|
|
|
760
583
|
if dismissible
|
|
584
|
+
close_svg = tag.svg(
|
|
585
|
+
safe_join([tag.line(x1: "18", y1: "6", x2: "6", y2: "18"), tag.line(x1: "6", y1: "6", x2: "18", y2: "18")]),
|
|
586
|
+
width: "14",
|
|
587
|
+
height: "14",
|
|
588
|
+
viewBox: "0 0 24 24",
|
|
589
|
+
fill: "none",
|
|
590
|
+
stroke: "currentColor",
|
|
591
|
+
"stroke-width": "2.5",
|
|
592
|
+
"stroke-linecap": "round",
|
|
593
|
+
"stroke-linejoin": "round"
|
|
594
|
+
)
|
|
761
595
|
parts << tag.button(
|
|
762
|
-
|
|
596
|
+
close_svg,
|
|
763
597
|
type: "button",
|
|
764
598
|
class: "odt-toast__close",
|
|
765
599
|
aria: { label: "Close" },
|
|
@@ -771,11 +605,6 @@ module OdtUi
|
|
|
771
605
|
tag.div(inner_content, **opts)
|
|
772
606
|
end
|
|
773
607
|
|
|
774
|
-
# Renders all active flash messages as ODT Toasts.
|
|
775
|
-
#
|
|
776
|
-
# @param flash_hash [ActionDispatch::Flash::FlashHash, Hash] Flash messages hash (default: flash)
|
|
777
|
-
# @param duration [Integer] Auto-dismiss duration in milliseconds
|
|
778
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
779
608
|
def odt_flash_toasts(flash_hash = flash, duration: 4000)
|
|
780
609
|
return "".html_safe if flash_hash.blank?
|
|
781
610
|
|
|
@@ -800,15 +629,6 @@ module OdtUi
|
|
|
800
629
|
safe_join(toasts)
|
|
801
630
|
end
|
|
802
631
|
|
|
803
|
-
# Generates a Turbo Stream action to render a toast asynchronously.
|
|
804
|
-
#
|
|
805
|
-
# @param message [String, nil]
|
|
806
|
-
# @param title [String, nil]
|
|
807
|
-
# @param variant [:success, :warning, :danger, :error, :info]
|
|
808
|
-
# @param target [String] Target container ID (default: "odt-toasts")
|
|
809
|
-
# @param action [:append, :prepend] Turbo stream action (default: :append)
|
|
810
|
-
# @param duration [Integer] Auto-dismiss duration in ms (default: 4000)
|
|
811
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
812
632
|
def odt_toast_stream(message = nil, title: nil, variant: :success, target: "odt-toasts", action: :append, duration: 4000, **options)
|
|
813
633
|
toast_html = odt_toast(message, title: title, variant: variant, duration: duration, **options)
|
|
814
634
|
|
|
@@ -821,17 +641,6 @@ module OdtUi
|
|
|
821
641
|
end
|
|
822
642
|
end
|
|
823
643
|
|
|
824
|
-
# Renders an ODT Modal dialog container with Stimulus controller.
|
|
825
|
-
#
|
|
826
|
-
# @param id [String, nil] Unique HTML ID of the modal
|
|
827
|
-
# @param title [String, nil] Header title text
|
|
828
|
-
# @param subtitle [String, nil] Header subtitle text
|
|
829
|
-
# @param size [:sm, :md, :lg, :xl, :full] Dialog width size (default: :md)
|
|
830
|
-
# @param radius [:none, :xs, :sm, :md, :lg, :xl, :"2xl", :"3xl", :"4xl", :full, nil]
|
|
831
|
-
# @param dismissible [Boolean] Whether to render the header close button (default: true)
|
|
832
|
-
# @param open [Boolean] Whether modal is initially open (default: false)
|
|
833
|
-
# @param prevent_backdrop_close [Boolean] Prevent closing when clicking overlay (default: false)
|
|
834
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
835
644
|
def odt_modal(id: nil, title: nil, subtitle: nil, size: :md, radius: :"2xl", dismissible: true, open: false, prevent_backdrop_close: false, **html_options, &block)
|
|
836
645
|
size_key = size.to_s.tr("_", "-")
|
|
837
646
|
radius_key = radius.to_s.tr("_", "-")
|
|
@@ -885,12 +694,6 @@ module OdtUi
|
|
|
885
694
|
end
|
|
886
695
|
end
|
|
887
696
|
|
|
888
|
-
# Renders an ODT Modal Header subcomponent.
|
|
889
|
-
#
|
|
890
|
-
# @param title [String, nil]
|
|
891
|
-
# @param subtitle [String, nil]
|
|
892
|
-
# @param dismissible [Boolean]
|
|
893
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
894
697
|
def odt_modal_header(title: nil, subtitle: nil, dismissible: true, **html_options, &block)
|
|
895
698
|
classes = ["odt-modal__header", html_options.delete(:class)]
|
|
896
699
|
opts = html_options.merge(class: classes.compact.reject(&:blank?).join(" "))
|
|
@@ -922,9 +725,6 @@ module OdtUi
|
|
|
922
725
|
end
|
|
923
726
|
end
|
|
924
727
|
|
|
925
|
-
# Renders an ODT Modal Body subcomponent.
|
|
926
|
-
#
|
|
927
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
928
728
|
def odt_modal_body(**html_options, &block)
|
|
929
729
|
classes = ["odt-modal__body", html_options.delete(:class)]
|
|
930
730
|
opts = html_options.merge(class: classes.compact.reject(&:blank?).join(" "))
|
|
@@ -932,9 +732,6 @@ module OdtUi
|
|
|
932
732
|
tag.div(content, **opts)
|
|
933
733
|
end
|
|
934
734
|
|
|
935
|
-
# Renders an ODT Modal Footer subcomponent.
|
|
936
|
-
#
|
|
937
|
-
# @return [ActiveSupport::SafeBuffer]
|
|
938
735
|
def odt_modal_footer(**html_options, &block)
|
|
939
736
|
classes = ["odt-modal__footer", html_options.delete(:class)]
|
|
940
737
|
opts = html_options.merge(class: classes.compact.reject(&:blank?).join(" "))
|
|
@@ -942,9 +739,256 @@ module OdtUi
|
|
|
942
739
|
tag.div(content, **opts)
|
|
943
740
|
end
|
|
944
741
|
|
|
742
|
+
def odt_dropdown_menu(open: false, close_on_click_outside: true, close_on_select: true, full_width: false, **html_options, &block)
|
|
743
|
+
classes = [
|
|
744
|
+
"odt-dropdown",
|
|
745
|
+
("odt-dropdown--full-width" if full_width),
|
|
746
|
+
html_options.delete(:class)
|
|
747
|
+
].compact.reject(&:blank?)
|
|
748
|
+
|
|
749
|
+
data_attrs = {
|
|
750
|
+
controller: "odt-dropdown",
|
|
751
|
+
odt_dropdown_open_value: open,
|
|
752
|
+
odt_dropdown_close_on_click_outside_value: close_on_click_outside,
|
|
753
|
+
odt_dropdown_close_on_select_value: close_on_select
|
|
754
|
+
}.merge(html_options.delete(:data) || {})
|
|
755
|
+
|
|
756
|
+
opts = html_options.merge(
|
|
757
|
+
class: classes.join(" "),
|
|
758
|
+
data: data_attrs
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
tag.div(**opts, &block)
|
|
762
|
+
end
|
|
763
|
+
|
|
764
|
+
def odt_dropdown_trigger(text = nil, label: nil, helper: nil, helper_text: nil, error: nil, variant: :outlined, size: :md, radius: :xl, chevron: true, placeholder: nil, left_icon: nil, right_icon: nil, full_width: false, disabled: false, **html_options, &block)
|
|
765
|
+
variant_key = variant.to_s.tr("_", "-")
|
|
766
|
+
size_key = size.to_s.tr("_", "-")
|
|
767
|
+
radius_key = radius.to_s.tr("_", "-")
|
|
768
|
+
resolved_helper = helper.presence || helper_text.presence
|
|
769
|
+
has_error = error.present?
|
|
770
|
+
|
|
771
|
+
button_classes = [
|
|
772
|
+
"odt-dropdown__trigger",
|
|
773
|
+
"odt-dropdown__trigger--#{variant_key}",
|
|
774
|
+
"odt-dropdown__trigger--#{size_key}",
|
|
775
|
+
"odt-radius-#{radius_key}",
|
|
776
|
+
("w-full" if full_width),
|
|
777
|
+
("odt-dropdown__trigger--error" if has_error),
|
|
778
|
+
html_options.delete(:class)
|
|
779
|
+
].compact.reject(&:blank?)
|
|
780
|
+
|
|
781
|
+
btn_data = {
|
|
782
|
+
odt_dropdown_target: "trigger",
|
|
783
|
+
action: "click->odt-dropdown#toggle keydown->odt-dropdown#navigate"
|
|
784
|
+
}.merge(html_options.delete(:data) || {})
|
|
785
|
+
|
|
786
|
+
btn_opts = html_options.merge(
|
|
787
|
+
type: "button",
|
|
788
|
+
class: button_classes.join(" "),
|
|
789
|
+
disabled: disabled,
|
|
790
|
+
aria: { haspopup: "menu", expanded: "false" },
|
|
791
|
+
data: btn_data
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
button_content = block_given? ? capture(&block) : text
|
|
795
|
+
content_parts = []
|
|
796
|
+
if left_icon.present?
|
|
797
|
+
content_parts << render_icon_element(left_icon)
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
span_class = button_content.blank? && placeholder.present? ? "odt-dropdown__trigger-text odt-dropdown__placeholder" : "odt-dropdown__trigger-text"
|
|
801
|
+
content_parts << tag.span(button_content.presence || placeholder, class: span_class, data: { odt_dropdown_target: "text" })
|
|
802
|
+
|
|
803
|
+
if right_icon.present?
|
|
804
|
+
content_parts << render_icon_element(right_icon)
|
|
805
|
+
elsif chevron
|
|
806
|
+
content_parts << tag.span(class: "odt-dropdown__chevron", data: { odt_dropdown_target: "chevron" }) do
|
|
807
|
+
tag.svg(width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round") do
|
|
808
|
+
tag.polyline(points: "6 9 12 15 18 9")
|
|
809
|
+
end
|
|
810
|
+
end
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
button_node = tag.button(safe_join(content_parts), **btn_opts)
|
|
814
|
+
|
|
815
|
+
if label.present? || resolved_helper.present? || has_error
|
|
816
|
+
container_classes = [
|
|
817
|
+
"odt-dropdown__container",
|
|
818
|
+
("odt-dropdown__container--full-width" if full_width)
|
|
819
|
+
].compact.join(" ")
|
|
820
|
+
|
|
821
|
+
tag.div(class: container_classes) do
|
|
822
|
+
wrapper_parts = []
|
|
823
|
+
wrapper_parts << tag.span(label, class: "odt-dropdown__label") if label.present?
|
|
824
|
+
wrapper_parts << button_node
|
|
825
|
+
if has_error
|
|
826
|
+
wrapper_parts << tag.p(error, class: "odt-error-text", role: "alert")
|
|
827
|
+
elsif resolved_helper.present?
|
|
828
|
+
wrapper_parts << tag.p(resolved_helper, class: "odt-helper-text")
|
|
829
|
+
end
|
|
830
|
+
safe_join(wrapper_parts)
|
|
831
|
+
end
|
|
832
|
+
else
|
|
833
|
+
button_node
|
|
834
|
+
end
|
|
835
|
+
end
|
|
836
|
+
|
|
837
|
+
def odt_dropdown_content(align: :start, side: :bottom, radius: :xl, shadow: :dropdown, full_width: false, **html_options, &block)
|
|
838
|
+
align_key = align.to_s.tr("_", "-")
|
|
839
|
+
side_key = side.to_s.tr("_", "-")
|
|
840
|
+
radius_key = radius.to_s.tr("_", "-")
|
|
841
|
+
shadow_key = shadow.to_s.tr("_", "-")
|
|
842
|
+
|
|
843
|
+
classes = [
|
|
844
|
+
"odt-dropdown__content",
|
|
845
|
+
"odt-dropdown__content--align-#{align_key}",
|
|
846
|
+
"odt-dropdown__content--side-#{side_key}",
|
|
847
|
+
"odt-radius-#{radius_key}",
|
|
848
|
+
"odt-shadow-#{shadow_key}",
|
|
849
|
+
("w-full" if full_width),
|
|
850
|
+
"hidden",
|
|
851
|
+
html_options.delete(:class)
|
|
852
|
+
].compact.reject(&:blank?)
|
|
853
|
+
|
|
854
|
+
opts = html_options.merge(
|
|
855
|
+
role: "menu",
|
|
856
|
+
tabindex: "-1",
|
|
857
|
+
class: classes.join(" "),
|
|
858
|
+
data: { odt_dropdown_target: "content" }.merge(html_options.delete(:data) || {})
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
tag.div(**opts, &block)
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
def odt_dropdown_item(text = nil, href: nil, left_icon: nil, right_icon: nil, shortcut: nil, destructive: false, disabled: false, inset: false, value: nil, **html_options, &block)
|
|
865
|
+
classes = [
|
|
866
|
+
"odt-dropdown__item",
|
|
867
|
+
("odt-dropdown__item--destructive" if destructive),
|
|
868
|
+
("odt-dropdown__item--disabled" if disabled),
|
|
869
|
+
("odt-dropdown__item--inset" if inset),
|
|
870
|
+
html_options.delete(:class)
|
|
871
|
+
].compact.reject(&:blank?)
|
|
872
|
+
|
|
873
|
+
data_attrs = {
|
|
874
|
+
action: "click->odt-dropdown#select"
|
|
875
|
+
}
|
|
876
|
+
data_attrs[:value] = value if value.present?
|
|
877
|
+
data_attrs.merge!(html_options.delete(:data) || {})
|
|
878
|
+
|
|
879
|
+
opts = html_options.merge(
|
|
880
|
+
role: "menuitem",
|
|
881
|
+
tabindex: disabled ? "-1" : "0",
|
|
882
|
+
class: classes.join(" "),
|
|
883
|
+
data: data_attrs
|
|
884
|
+
)
|
|
885
|
+
opts[:disabled] = true if disabled
|
|
886
|
+
|
|
887
|
+
content = block_given? ? capture(&block) : text
|
|
888
|
+
parts = []
|
|
889
|
+
if left_icon.present?
|
|
890
|
+
parts << tag.span(render_icon_element(left_icon), class: "odt-dropdown__item-icon")
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
parts << tag.span(content, class: "odt-dropdown__item-content")
|
|
894
|
+
|
|
895
|
+
if shortcut.present?
|
|
896
|
+
parts << tag.span(shortcut, class: "odt-dropdown__shortcut")
|
|
897
|
+
end
|
|
898
|
+
|
|
899
|
+
if right_icon.present?
|
|
900
|
+
parts << tag.span(render_icon_element(right_icon), class: "odt-dropdown__item-right-icon")
|
|
901
|
+
end
|
|
902
|
+
|
|
903
|
+
inner_content = safe_join(parts)
|
|
904
|
+
|
|
905
|
+
if href.present?
|
|
906
|
+
link_to(inner_content, href, opts)
|
|
907
|
+
else
|
|
908
|
+
opts[:type] = "button"
|
|
909
|
+
tag.button(inner_content, **opts)
|
|
910
|
+
end
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
def odt_dropdown_label(text = nil, inset: false, **html_options, &block)
|
|
914
|
+
classes = [
|
|
915
|
+
"odt-dropdown__label-heading",
|
|
916
|
+
("odt-dropdown__label-heading--inset" if inset),
|
|
917
|
+
html_options.delete(:class)
|
|
918
|
+
].compact.reject(&:blank?)
|
|
919
|
+
|
|
920
|
+
content = block_given? ? capture(&block) : text
|
|
921
|
+
tag.div(content, class: classes.join(" "), **html_options)
|
|
922
|
+
end
|
|
923
|
+
|
|
924
|
+
def odt_dropdown_separator(**html_options)
|
|
925
|
+
classes = ["odt-dropdown__separator", html_options.delete(:class)].compact.reject(&:blank?)
|
|
926
|
+
tag.hr(class: classes.join(" "), **html_options)
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
def odt_dropdown_shortcut(text = nil, **html_options, &block)
|
|
930
|
+
classes = ["odt-dropdown__shortcut", html_options.delete(:class)].compact.reject(&:blank?)
|
|
931
|
+
content = block_given? ? capture(&block) : text
|
|
932
|
+
tag.span(content, class: classes.join(" "), **html_options)
|
|
933
|
+
end
|
|
934
|
+
|
|
945
935
|
private
|
|
946
936
|
|
|
947
|
-
|
|
937
|
+
def render_custom_select(label: nil, options: [], variant: :outlined, size: :md, radius: nil, prompt: nil, error: nil, helper: nil, helper_text: nil, success: nil, disabled: false, full_width: true, **html_options)
|
|
938
|
+
resolved_helper = helper.presence || helper_text.presence
|
|
939
|
+
name = html_options.delete(:name)
|
|
940
|
+
id = html_options.delete(:id)
|
|
941
|
+
selected_value = html_options.delete(:selected) || html_options.delete(:value)
|
|
942
|
+
|
|
943
|
+
items = if options.is_a?(Array)
|
|
944
|
+
options.map do |opt|
|
|
945
|
+
if opt.is_a?(Array)
|
|
946
|
+
[opt[0].to_s, opt[1].to_s]
|
|
947
|
+
else
|
|
948
|
+
[opt.to_s, opt.to_s]
|
|
949
|
+
end
|
|
950
|
+
end
|
|
951
|
+
else
|
|
952
|
+
[]
|
|
953
|
+
end
|
|
954
|
+
|
|
955
|
+
selected_pair = items.find { |_, val| val == selected_value.to_s }
|
|
956
|
+
display_text = selected_pair ? selected_pair[0] : (prompt || items.first&.first || "")
|
|
957
|
+
current_value = selected_pair ? selected_pair[1] : (prompt ? "" : items.first&.last)
|
|
958
|
+
|
|
959
|
+
odt_dropdown_menu(full_width: full_width) do
|
|
960
|
+
parts = []
|
|
961
|
+
if name.present?
|
|
962
|
+
parts << tag.input(type: "hidden", name: name, value: current_value, id: id, data: { odt_dropdown_target: "hiddenInput" })
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
parts << odt_dropdown_trigger(
|
|
966
|
+
display_text,
|
|
967
|
+
label: label,
|
|
968
|
+
helper: resolved_helper,
|
|
969
|
+
error: error,
|
|
970
|
+
variant: variant,
|
|
971
|
+
size: size,
|
|
972
|
+
radius: radius || :xl,
|
|
973
|
+
full_width: full_width,
|
|
974
|
+
disabled: disabled
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
parts << odt_dropdown_content(radius: radius || :xl, full_width: full_width) do
|
|
978
|
+
item_parts = []
|
|
979
|
+
if prompt.present?
|
|
980
|
+
item_parts << odt_dropdown_item(prompt, value: "")
|
|
981
|
+
end
|
|
982
|
+
items.each do |lbl, val|
|
|
983
|
+
item_parts << odt_dropdown_item(lbl, value: val)
|
|
984
|
+
end
|
|
985
|
+
safe_join(item_parts)
|
|
986
|
+
end
|
|
987
|
+
|
|
988
|
+
safe_join(parts)
|
|
989
|
+
end
|
|
990
|
+
end
|
|
991
|
+
|
|
948
992
|
def normalize_button_variant(variant)
|
|
949
993
|
case variant.to_sym
|
|
950
994
|
when :light
|
|
@@ -964,7 +1008,6 @@ module OdtUi
|
|
|
964
1008
|
end
|
|
965
1009
|
end
|
|
966
1010
|
|
|
967
|
-
# Computes initials from a full name (e.g., "John Doe" -> "JD")
|
|
968
1011
|
def compute_avatar_initials(name)
|
|
969
1012
|
return nil if name.blank?
|
|
970
1013
|
|
|
@@ -976,7 +1019,6 @@ module OdtUi
|
|
|
976
1019
|
end
|
|
977
1020
|
end
|
|
978
1021
|
|
|
979
|
-
# Safely renders an icon (SVG or HTML class)
|
|
980
1022
|
def render_icon_element(icon)
|
|
981
1023
|
return nil if icon.blank?
|
|
982
1024
|
|