rails-active-ui 0.3.8 → 0.4.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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +195 -6
  3. data/Rakefile +1 -1
  4. data/app/components/accordion_component.rb +41 -24
  5. data/app/components/accordion_item_component.rb +40 -0
  6. data/app/components/ad_component.rb +1 -1
  7. data/app/components/api_component.rb +1 -1
  8. data/app/components/breadcrumb_component.rb +1 -1
  9. data/app/components/button_group_component.rb +36 -0
  10. data/app/components/button_to_component.rb +1 -1
  11. data/app/components/calendar_component.rb +24 -20
  12. data/app/components/card_component.rb +5 -3
  13. data/app/components/comment_component.rb +5 -3
  14. data/app/components/comment_group_component.rb +27 -0
  15. data/app/components/comment_reply_component.rb +47 -0
  16. data/app/components/comment_reply_group_component.rb +25 -0
  17. data/app/components/dimmer_component.rb +1 -1
  18. data/app/components/divider_component.rb +1 -1
  19. data/app/components/dropdown_component.rb +6 -2
  20. data/app/components/embed_component.rb +1 -1
  21. data/app/components/emoji_component.rb +1 -1
  22. data/app/components/feed_component.rb +16 -4
  23. data/app/components/feed_item_component.rb +85 -0
  24. data/app/components/field_component.rb +42 -0
  25. data/app/components/flag_component.rb +1 -1
  26. data/app/components/flyout_component.rb +3 -2
  27. data/app/components/h_stack_component.rb +1 -1
  28. data/app/components/header_component.rb +2 -2
  29. data/app/components/image_component.rb +1 -1
  30. data/app/components/input_component.rb +22 -41
  31. data/app/components/item_component.rb +3 -2
  32. data/app/components/item_group_component.rb +1 -1
  33. data/app/components/list_component.rb +2 -2
  34. data/app/components/list_content_component.rb +27 -0
  35. data/app/components/list_description_component.rb +17 -0
  36. data/app/components/list_header_component.rb +28 -0
  37. data/app/components/list_item_component.rb +37 -0
  38. data/app/components/loader_component.rb +1 -1
  39. data/app/components/menu_component.rb +1 -1
  40. data/app/components/modal_component.rb +2 -1
  41. data/app/components/nag_component.rb +1 -1
  42. data/app/components/overlay_component.rb +1 -1
  43. data/app/components/placeholder_component.rb +37 -13
  44. data/app/components/popup_component.rb +1 -1
  45. data/app/components/progress_component.rb +1 -1
  46. data/app/components/pusher_component.rb +1 -1
  47. data/app/components/rail_component.rb +1 -1
  48. data/app/components/rating_component.rb +1 -1
  49. data/app/components/reveal_component.rb +3 -2
  50. data/app/components/row_component.rb +4 -0
  51. data/app/components/search_component.rb +1 -1
  52. data/app/components/segment_group_component.rb +1 -1
  53. data/app/components/shape_component.rb +1 -1
  54. data/app/components/sidebar_component.rb +1 -1
  55. data/app/components/site_component.rb +1 -1
  56. data/app/components/slider_component.rb +1 -1
  57. data/app/components/state_component.rb +1 -1
  58. data/app/components/statistic_component.rb +3 -2
  59. data/app/components/step_component.rb +2 -2
  60. data/app/components/step_group_component.rb +1 -1
  61. data/app/components/sticky_component.rb +1 -1
  62. data/app/components/style_component.rb +1 -1
  63. data/app/components/sub_accordion_component.rb +25 -0
  64. data/app/components/sub_header_component.rb +1 -1
  65. data/app/components/sub_menu_component.rb +1 -1
  66. data/app/components/table_cell_component.rb +2 -2
  67. data/app/components/table_component.rb +2 -2
  68. data/app/components/tag_component.rb +57 -0
  69. data/app/components/tag_group_component.rb +33 -0
  70. data/app/components/text_component.rb +1 -1
  71. data/app/components/toast_component.rb +1 -1
  72. data/app/components/transition_component.rb +2 -2
  73. data/app/components/v_stack_component.rb +1 -1
  74. data/app/components/visibility_component.rb +1 -1
  75. data/app/helpers/component_helper.rb +23 -6
  76. data/app/helpers/ui/fomantic_form_builder.rb +155 -201
  77. data/app/javascript/ui/controllers/fui_calendar_controller.js +18 -8
  78. data/lib/ui/version.rb +1 -1
  79. metadata +15 -3
  80. data/app/components/checkbox_component.rb +0 -41
  81. data/app/components/label_component.rb +0 -49
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # CommentReplyGroup — nested reply group within a Comment.
4
+ #
5
+ # Usage:
6
+ # Comment { |c|
7
+ # c.avatar { ... }
8
+ # c.author { text "Elliot" }
9
+ # CommentReplyGroup {
10
+ # CommentReply { |c| ... }
11
+ # }
12
+ # }
13
+
14
+ class CommentReplyGroupComponent < Component
15
+ attribute :collapsed, :boolean, default: false
16
+
17
+ def to_s
18
+ classes = class_names(
19
+ { "collapsed" => collapsed },
20
+ "comments"
21
+ )
22
+
23
+ tag.div(**merge_html_options(class: classes)) { @content }
24
+ end
25
+ end
@@ -25,6 +25,6 @@ class DimmerComponent < Component
25
25
 
26
26
  content_el = @content.present? ? tag.div(class: "content") { @content } : nil
27
27
 
28
- tag.div(class: classes, data: { controller: "fui-dimmer" }) { content_el }
28
+ tag.div(**merge_html_options(class: classes, data: { controller: "fui-dimmer" })) { content_el }
29
29
  end
30
30
  end
@@ -25,6 +25,6 @@ class DividerComponent < Component
25
25
  "divider"
26
26
  )
27
27
 
28
- tag.div(class: classes) { @content }
28
+ tag.div(**merge_html_options(class: classes)) { @content }
29
29
  end
30
30
  end
@@ -28,6 +28,8 @@ class DropdownComponent < Component
28
28
  attribute :disabled, :boolean, default: false
29
29
  attribute :action, :string, default: nil
30
30
 
31
+ slot :trigger
32
+
31
33
  def to_s
32
34
  classes = class_names(
33
35
  "ui",
@@ -49,17 +51,19 @@ class DropdownComponent < Component
49
51
  hidden_opts[:name] = name if name
50
52
 
51
53
  search_el = search ? tag.input(class: "search") : nil
54
+ trigger_el = @slots[:trigger]
52
55
  text_el = if inline
53
56
  tag.div(class: "text") { tag.h2(class: "ui header") { placeholder || "" } }
54
- else
57
+ elsif !trigger_el
55
58
  tag.div(class: "text") { placeholder || "" }
56
59
  end
57
60
  menu_el = tag.div(class: "menu") { @content }
58
61
 
59
- tag.div(class: classes, data: data) {
62
+ tag.div(**merge_html_options(class: classes, data: data)) {
60
63
  safe_join([
61
64
  tag.input(**hidden_opts),
62
65
  search_el,
66
+ trigger_el,
63
67
  text_el,
64
68
  tag.i(class: "dropdown icon"),
65
69
  menu_el
@@ -25,7 +25,7 @@ class EmbedComponent < Component
25
25
  icon_el = tag.i(class: "#{icon} icon")
26
26
  placeholder_el = placeholder ? tag.img(class: "placeholder", src: placeholder) : nil
27
27
 
28
- tag.div(class: "ui embed", data: data) {
28
+ tag.div(**merge_html_options(class: "ui embed", data: data)) {
29
29
  safe_join([ icon_el, placeholder_el, @content ])
30
30
  }
31
31
  end
@@ -10,6 +10,6 @@ class EmojiComponent < Component
10
10
  attribute :name, :string, default: nil
11
11
 
12
12
  def to_s
13
- tag.em(class: "small", data: { emoji: ":#{name}:" })
13
+ tag.em(**merge_html_options(class: "small", data: { emoji: ":#{name}:" }))
14
14
  end
15
15
  end
@@ -3,20 +3,32 @@
3
3
  # Feed — activity/event feed.
4
4
  #
5
5
  # Usage:
6
- # Feed(size: :small) {
7
- # text '<div class="event">...</div>'.html_safe
6
+ # Feed(size: "small") {
7
+ # FeedEvent { |e| ... }
8
8
  # }
9
9
 
10
10
  class FeedComponent < Component
11
- attribute :size, :string, default: nil
11
+ attribute :size, :string, default: nil
12
+ attribute :inverted, :boolean, default: false
13
+ attribute :connected, :boolean, default: false
14
+ attribute :ordered, :boolean, default: false
15
+ attribute :divided, :boolean, default: false
16
+ attribute :disabled, :boolean, default: false
17
+ attribute :color, :string, default: nil
12
18
 
13
19
  def to_s
14
20
  classes = class_names(
15
21
  "ui",
16
22
  size,
23
+ color,
24
+ { "inverted" => inverted },
25
+ { "connected" => connected },
26
+ { "ordered" => ordered },
27
+ { "divided" => divided },
28
+ { "disabled" => disabled },
17
29
  "feed"
18
30
  )
19
31
 
20
- tag.div(class: classes) { @content }
32
+ tag.div(**merge_html_options(class: classes)) { @content }
21
33
  end
22
34
  end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ # FeedItem — a single item/event in a Feed.
4
+ #
5
+ # Usage:
6
+ # FeedItem { |e|
7
+ # e.label { Image(src: "/images/avatar/small/elliot.jpg") }
8
+ # e.summary {
9
+ # LinkTo(class: "user") { "Elliot Fu" }
10
+ # text " added you as a friend"
11
+ # }
12
+ # e.date { "1 Hour Ago" }
13
+ # e.meta {
14
+ # LinkTo(class: "like") {
15
+ # Icon(name: "like")
16
+ # text " 4 Likes"
17
+ # }
18
+ # }
19
+ # e.extra_text { "Some additional text" }
20
+ # e.extra_images {
21
+ # LinkTo { Image(src: "/images/wireframe/image.png") }
22
+ # }
23
+ # }
24
+
25
+ class FeedItemComponent < Component
26
+ attribute :disabled, :boolean, default: false
27
+ attribute :color, :string, default: nil
28
+
29
+ slot :label
30
+ slot :summary
31
+ slot :date
32
+ slot :date_inline
33
+ slot :meta
34
+ slot :extra_text
35
+ slot :extra_images
36
+
37
+ def to_s
38
+ classes = class_names(
39
+ { "disabled" => disabled },
40
+ color,
41
+ "event"
42
+ )
43
+
44
+ label_el = if @slots[:label]
45
+ tag.div(class: "label") { @slots[:label] }
46
+ end
47
+
48
+ content_parts = []
49
+
50
+ # Date can appear at the top of content (outside summary)
51
+ if @slots[:date]
52
+ content_parts << tag.div(class: "date") { @slots[:date] }
53
+ end
54
+
55
+ # Summary with optional inline date
56
+ if @slots[:summary]
57
+ summary_inner = @slots[:summary]
58
+ if @slots[:date_inline]
59
+ summary_inner = safe_join([ summary_inner, tag.div(class: "date") { @slots[:date_inline] } ])
60
+ end
61
+ content_parts << tag.div(class: "summary") { summary_inner }
62
+ end
63
+
64
+ if @slots[:extra_text]
65
+ content_parts << tag.div(class: "extra text") { @slots[:extra_text] }
66
+ end
67
+
68
+ if @slots[:extra_images]
69
+ content_parts << tag.div(class: "extra images") { @slots[:extra_images] }
70
+ end
71
+
72
+ if @slots[:meta]
73
+ content_parts << tag.div(class: "meta") { @slots[:meta] }
74
+ end
75
+
76
+ loose_content = @slots.values.any? ? nil : @content.presence
77
+ content_el = if content_parts.any? || loose_content.present?
78
+ tag.div(class: "content") { safe_join([ *content_parts, loose_content ]) }
79
+ end
80
+
81
+ tag.div(**merge_html_options(class: classes)) {
82
+ safe_join([ label_el, content_el ])
83
+ }
84
+ end
85
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Field — Fomantic UI field wrapper.
4
+ #
5
+ # Renders <div class="... field"> for use inside forms.
6
+ #
7
+ # Usage:
8
+ #
9
+ # Field {
10
+ # Input(icon: "user", icon_position: "left") {
11
+ # TextField(:email, placeholder: "E-mail address")
12
+ # }
13
+ # }
14
+ #
15
+ # Field(required: true, label: "Email") {
16
+ # TextField(:email, placeholder: "you@example.com")
17
+ # }
18
+
19
+ class FieldComponent < Component
20
+ attribute :required, :boolean, default: false
21
+ attribute :disabled, :boolean, default: false
22
+ attribute :error, :boolean, default: false
23
+ attribute :warning, :boolean, default: false
24
+ attribute :inline, :boolean, default: false
25
+ attribute :width, :string, default: nil
26
+ attribute :label, :string, default: nil
27
+
28
+ def to_s
29
+ classes = class_names(
30
+ width,
31
+ { "required" => required, "disabled" => disabled,
32
+ "error" => error, "warning" => warning, "inline" => inline },
33
+ "field"
34
+ )
35
+
36
+ label_el = label ? tag.label(label) : nil
37
+
38
+ tag.div(**merge_html_options(class: classes)) {
39
+ safe_join([ label_el, @content ].compact)
40
+ }
41
+ end
42
+ end
@@ -10,6 +10,6 @@ class FlagComponent < Component
10
10
  attribute :country, :string, default: nil
11
11
 
12
12
  def to_s
13
- tag.i(class: "#{country} flag")
13
+ tag.i(**merge_html_options(class: "#{country} flag"))
14
14
  end
15
15
  end
@@ -34,8 +34,9 @@ class FlyoutComponent < Component
34
34
  content_el = @slots[:content] ? tag.div(class: "content") { @slots[:content] } : nil
35
35
  actions_el = @slots[:actions] ? tag.div(class: "actions") { @slots[:actions] } : nil
36
36
 
37
- tag.div(class: classes, data: data) {
38
- safe_join([ close_el, header_el, content_el, @content.presence, actions_el ])
37
+ loose_content = @slots.values.any? ? nil : @content.presence
38
+ tag.div(**merge_html_options(class: classes, data: data)) {
39
+ safe_join([ close_el, header_el, content_el, loose_content, actions_el ])
39
40
  }
40
41
  end
41
42
  end
@@ -28,7 +28,7 @@ class HStackComponent < Component
28
28
  opts[:class] = classes.join(" ") unless classes.empty?
29
29
  opts[:style] = style_parts unless style_parts.empty?
30
30
 
31
- tag.div(**opts) { @content }
31
+ tag.div(**merge_html_options(**opts)) { @content }
32
32
  end
33
33
 
34
34
  private
@@ -45,14 +45,14 @@ class HeaderComponent < Component
45
45
 
46
46
  if image && @slots[:header_image]
47
47
  # Image header: image outside content div, text + sub header inside content div
48
- tag.public_send(tag_name, class: classes) {
48
+ tag.public_send(tag_name, **merge_html_options(class: classes)) {
49
49
  safe_join([
50
50
  @slots[:header_image],
51
51
  tag.div(class: "content") { @content }
52
52
  ])
53
53
  }
54
54
  else
55
- tag.public_send(tag_name, class: classes) {
55
+ tag.public_send(tag_name, **merge_html_options(class: classes)) {
56
56
  safe_join([ icon_el, @content ])
57
57
  }
58
58
  end
@@ -41,6 +41,6 @@ class ImageComponent < Component
41
41
  opts[:height] = height if height
42
42
  opts[:style] = "flex-shrink:0" unless shrink
43
43
 
44
- tag.img(**opts)
44
+ tag.img(**merge_html_options(**opts))
45
45
  end
46
46
  end
@@ -1,13 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Input — text input fields with icon, labeled, and action variants.
3
+ # Input — Fomantic UI input wrapper.
4
+ #
5
+ # Renders <div class="ui ... input"> with optional icon. The actual <input>
6
+ # element comes from the block (e.g. a form builder TextField) or is
7
+ # auto-generated as a basic <input type="text"> when no block is given.
4
8
  #
5
9
  # Usage:
6
- # Input(placeholder: "Search...", icon: "search")
7
- # Input(icon: "users", icon_position: "left", placeholder: "Find users...")
8
- # Input(labeled: true) { text "$" }
9
- # Input(name: "email", label: "Email", placeholder: "you@example.com")
10
- # Input(name: "password", label: "Password", input_type: "password")
10
+ #
11
+ # # Standalone (no form builder) auto-generates <input>:
12
+ # Input(icon: "search", placeholder: "Search...")
13
+ #
14
+ # # Wrapping a form builder field:
15
+ # Input(icon: "user", icon_position: "left") {
16
+ # TextField(:email, placeholder: "E-mail address")
17
+ # }
18
+ #
19
+ # # Labeled input:
20
+ # Input(labeled: true) { Tag { "http://" } }
11
21
 
12
22
  class InputComponent < Component
13
23
  attribute :icon, :string, default: nil
@@ -22,54 +32,25 @@ class InputComponent < Component
22
32
  attribute :error, :boolean, default: false
23
33
  attribute :inverted, :boolean, default: false
24
34
  attribute :placeholder, :string, default: nil
25
- attribute :input_type, :string, default: "text"
26
- attribute :name, :string, default: nil
27
- attribute :value, :string, default: nil
28
- attribute :label, :string, default: nil
29
- attribute :id, :string, default: nil
30
35
 
31
36
  def to_s
32
- input_field = input_element
33
-
34
- if label
35
- tag.div(class: "field") {
36
- safe_join([
37
- tag.label(label, for: id || name),
38
- input_field
39
- ])
40
- }
41
- else
42
- input_field
43
- end
44
- end
45
-
46
- private
47
-
48
- def input_element
49
37
  classes = class_names(
50
38
  "ui",
51
39
  size,
52
40
  { "left icon" => icon && icon_position == "left",
53
41
  "icon" => icon && icon_position != "left",
54
- "labeled" => labeled, "action" => action, "transparent" => transparent,
55
- "fluid" => fluid, "loading" => loading, "disabled" => disabled,
42
+ "labeled" => labeled, "action" => action,
43
+ "transparent" => transparent, "fluid" => fluid,
44
+ "loading" => loading, "disabled" => disabled,
56
45
  "error" => error, "inverted" => inverted },
57
46
  "input"
58
47
  )
59
48
 
60
- input_opts = { type: input_type }
61
- input_opts[:placeholder] = placeholder if placeholder
62
- input_opts[:name] = name if name
63
- input_opts[:value] = value if value
64
- input_opts[:disabled] = "disabled" if disabled
65
- input_opts[:id] = id if id
66
- input_opts[:aria] = @html_options[:aria] if @html_options&.dig(:aria)
67
-
68
49
  icon_el = icon ? tag.i(class: "#{icon} icon") : nil
69
- input_el = tag.input(**input_opts)
50
+ content = @content.present? ? @content : tag.input(type: "text", placeholder: placeholder)
70
51
 
71
- tag.div(class: classes) {
72
- safe_join([ @content, input_el, icon_el ])
52
+ tag.div(**merge_html_options(class: classes)) {
53
+ safe_join([ content, icon_el ].compact)
73
54
  }
74
55
  end
75
56
  end
@@ -32,8 +32,9 @@ class ItemComponent < Component
32
32
 
33
33
  content_el = content_parts.any? ? tag.div(class: "content") { safe_join(content_parts) } : nil
34
34
 
35
- tag.div(class: "item") {
36
- safe_join([ image_el, content_el, @content.presence ])
35
+ loose_content = @slots.values.any? ? nil : @content.presence
36
+ tag.div(**merge_html_options(class: "item")) {
37
+ safe_join([ image_el, content_el, loose_content ])
37
38
  }
38
39
  end
39
40
  end
@@ -25,6 +25,6 @@ class ItemGroupComponent < Component
25
25
  "items"
26
26
  )
27
27
 
28
- tag.div(class: classes) { @content }
28
+ tag.div(**merge_html_options(class: classes)) { @content }
29
29
  end
30
30
  end
@@ -31,9 +31,9 @@ class ListComponent < Component
31
31
  )
32
32
 
33
33
  if ordered
34
- tag.ol(class: classes) { @content }
34
+ tag.ol(**merge_html_options(class: classes)) { @content }
35
35
  else
36
- tag.div(class: classes) { @content }
36
+ tag.div(**merge_html_options(class: classes)) { @content }
37
37
  end
38
38
  end
39
39
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ListContent — content wrapper within a ListItem.
4
+ #
5
+ # Usage:
6
+ # ListItem {
7
+ # Image(src: "avatar.png", class: "avatar")
8
+ # ListContent {
9
+ # ListHeader { text "Rachel" }
10
+ # ListDescription { text "Last seen just now." }
11
+ # }
12
+ # }
13
+
14
+ class ListContentComponent < Component
15
+ attribute :aligned, :string, default: nil
16
+ attribute :floated, :string, default: nil
17
+
18
+ def to_s
19
+ classes = class_names(
20
+ floated && "#{floated} floated",
21
+ aligned && "#{aligned} aligned",
22
+ "content"
23
+ )
24
+
25
+ tag.div(**merge_html_options(class: classes)) { @content }
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ListDescription — description text within a ListItem or ListContent.
4
+ #
5
+ # Usage:
6
+ # ListItem {
7
+ # ListContent {
8
+ # ListHeader { text "Krolewskie Jadlo" }
9
+ # ListDescription { text "An excellent polish restaurant." }
10
+ # }
11
+ # }
12
+
13
+ class ListDescriptionComponent < Component
14
+ def to_s
15
+ tag.div(**merge_html_options(class: "description")) { @content }
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ListHeader — header text within a ListItem or ListContent.
4
+ #
5
+ # Usage:
6
+ # ListItem {
7
+ # ListHeader { text "New York City" }
8
+ # text "A lovely city"
9
+ # }
10
+ #
11
+ # ListItem {
12
+ # ListContent {
13
+ # ListHeader(href: "#") { text "Rachel" }
14
+ # ListDescription { text "Last seen just now." }
15
+ # }
16
+ # }
17
+
18
+ class ListHeaderComponent < Component
19
+ attribute :href, :string, default: nil
20
+
21
+ def to_s
22
+ if href
23
+ tag.a(**merge_html_options(class: "header", href: href)) { @content }
24
+ else
25
+ tag.div(**merge_html_options(class: "header")) { @content }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ListItem — individual item within a List.
4
+ #
5
+ # Usage:
6
+ # List {
7
+ # ListItem { text "Apples" }
8
+ # ListItem(icon: "users") { ListContent { text "Fomantic UI" } }
9
+ # ListItem(href: "#") { text "Link item" }
10
+ # }
11
+
12
+ class ListItemComponent < Component
13
+ attribute :href, :string, default: nil
14
+ attribute :active, :boolean, default: false
15
+ attribute :disabled, :boolean, default: false
16
+ attribute :icon, :string, default: nil
17
+ attribute :value, :string, default: nil
18
+
19
+ def to_s
20
+ classes = class_names(
21
+ { "active" => active, "disabled" => disabled },
22
+ "item"
23
+ )
24
+
25
+ icon_el = icon ? tag.i(class: "#{icon} icon") : nil
26
+ opts = { class: classes }
27
+ opts[:"data-value"] = value if value
28
+
29
+ inner = safe_join([ icon_el, @content ])
30
+
31
+ if href
32
+ tag.a(inner, **merge_html_options(**opts, href: href))
33
+ else
34
+ tag.div(inner, **merge_html_options(**opts))
35
+ end
36
+ end
37
+ end
@@ -28,6 +28,6 @@ class LoaderComponent < Component
28
28
  "loader"
29
29
  )
30
30
 
31
- tag.div(class: classes) { @content }
31
+ tag.div(**merge_html_options(class: classes)) { @content }
32
32
  end
33
33
  end
@@ -59,6 +59,6 @@ class MenuComponent < Component
59
59
  "menu"
60
60
  )
61
61
 
62
- tag.div(class: classes) { @content }
62
+ tag.div(**merge_html_options(class: classes)) { @content }
63
63
  end
64
64
  end
@@ -74,8 +74,9 @@ class ModalComponent < Component
74
74
  opts = merge_html_options(class: classes, data: data)
75
75
  opts[:id] = id if id
76
76
 
77
+ loose_content = @slots.values.any? ? nil : @content.presence
77
78
  tag.div(**opts) {
78
- safe_join([ close_el, header_el, content_el, @content.presence, actions_el ])
79
+ safe_join([ close_el, header_el, content_el, loose_content, actions_el ])
79
80
  }
80
81
  end
81
82
  end
@@ -18,7 +18,7 @@ class NagComponent < Component
18
18
 
19
19
  close_el = tag.i(class: "close icon")
20
20
 
21
- tag.div(class: classes, data: { controller: "fui-nag" }) {
21
+ tag.div(**merge_html_options(class: classes, data: { controller: "fui-nag" })) {
22
22
  safe_join([ close_el, @content ])
23
23
  }
24
24
  end
@@ -11,6 +11,6 @@
11
11
 
12
12
  class OverlayComponent < Component
13
13
  def to_s
14
- tag.div(class: "overlay") { @content }
14
+ tag.div(**merge_html_options(class: "overlay")) { @content }
15
15
  end
16
16
  end