klods-ruby 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f9b8c6d36dd6306ef10c5d07b9f6e6c6d800ea12d4cf2184ece44f68138ce290
4
+ data.tar.gz: 3f24505f91a3aa3c7003c50c5a2c14697b4301d83622f7cfa56664a84c30702d
5
+ SHA512:
6
+ metadata.gz: c0c76747cb39decbfa3255df8f7df27237bb51e1ba9d2da7f13c747345e7ac5576e78cb91c5875828cc47706190957da59bf0200a0703bd2cad9395304237feb
7
+ data.tar.gz: 9f3eb5fcf0d7c0d8f236518a2ecace6df9ead0891b90315e0148a5865992197bf409c6b19e4da0ccff699d04ed90d7c907570b228fb23b39becbd535844cbf1d
@@ -0,0 +1,35 @@
1
+ module Klods
2
+ module Builders
3
+ include Klods::Html
4
+ include Klods::Layout
5
+ include Klods::Utilities
6
+ include Klods::Components::Alert
7
+ include Klods::Components::Avatar
8
+ include Klods::Components::Badge
9
+ include Klods::Components::Box
10
+ include Klods::Components::Breadcrumb
11
+ include Klods::Components::Button
12
+ include Klods::Components::Card
13
+ include Klods::Components::Code
14
+ include Klods::Components::Details
15
+ include Klods::Components::Dl
16
+ include Klods::Components::Form
17
+ include Klods::Components::List
18
+ include Klods::Components::Modal
19
+ include Klods::Components::Nav
20
+ include Klods::Components::Prose
21
+ include Klods::Components::Table
22
+ include Klods::Components::Tabs
23
+ include Klods::Components::Toast
24
+ include Klods::Components::Tooltip
25
+ include Klods::Icons
26
+
27
+ def el(tag, a = nil, b = nil)
28
+ Core.el(tag, a, b)
29
+ end
30
+
31
+ def raw(html)
32
+ Core.raw(html)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,17 @@
1
+ module Klods
2
+ module Components
3
+ module Alert
4
+ def alert(a = nil, b = nil)
5
+ props, children = Core.normalize_args(a, b)
6
+ merged = {"role" => "alert"}.merge(props.transform_keys(&:to_s))
7
+ Core.build(
8
+ tag: "div", base: "klods-alert",
9
+ modifiers: {
10
+ variant: ->(v) { (v && v.to_s != "default") ? "klods-alert--#{v}" : nil }
11
+ },
12
+ props: merged, children: children
13
+ )
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ module Klods
2
+ module Components
3
+ module Avatar
4
+ AVATAR_SIZE_PX = {"small" => 12, "medium" => 20, "large" => 32}.freeze
5
+ USER_SVG_INNER = '<circle cx="8" cy="6" r="2.5" stroke="currentColor" stroke-width="1.5"/>' \
6
+ '<path stroke="currentColor" stroke-width="1.5" stroke-linecap="round" d="M2.5 14c0-2.8 2.5-5 5.5-5s5.5 2.2 5.5 5"/>'.freeze
7
+
8
+ private_constant :AVATAR_SIZE_PX, :USER_SVG_INNER
9
+
10
+ def avatar(props = nil)
11
+ props = (props || {}).transform_keys(&:to_s)
12
+ src = props.delete("src")
13
+ name = props.delete("name")
14
+ size = (props.delete("size") || "medium").to_s
15
+ extra_class = props.delete("class")
16
+
17
+ has_initials = src.nil? && !name.nil?
18
+ cls = Core.class_names(
19
+ "klods-avatar",
20
+ (size != "medium") ? "klods-avatar--#{size}" : nil,
21
+ has_initials ? "klods-avatar--initials" : nil,
22
+ Core.resolve_class(extra_class)
23
+ )
24
+
25
+ content = if src
26
+ Core.el("img", {"src" => src, "alt" => name.to_s, "class" => "klods-avatar__img"})
27
+ elsif name
28
+ initials = name.strip.split(/\s+/).first(2).map { |w| w[0]&.upcase || "" }.join
29
+ Core.el("span", {"aria-hidden" => "true"}, initials)
30
+ else
31
+ px = AVATAR_SIZE_PX[size] || 20
32
+ svg = %(<svg width="#{px}" height="#{px}" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">#{USER_SVG_INNER}</svg>)
33
+ Core.el("span", {"aria-hidden" => "true", "class" => "klods-icon"}, Core.raw(svg))
34
+ end
35
+
36
+ span_attrs = props.merge("class" => cls.empty? ? nil : cls).compact
37
+ if has_initials
38
+ span_attrs["role"] = "img"
39
+ span_attrs["aria-label"] = name
40
+ end
41
+
42
+ Core.el("span", span_attrs, content)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,16 @@
1
+ module Klods
2
+ module Components
3
+ module Badge
4
+ def badge(a = nil, b = nil)
5
+ props, children = Core.normalize_args(a, b)
6
+ Core.build(
7
+ tag: "span", base: "klods-badge",
8
+ modifiers: {
9
+ variant: ->(v) { (v && v.to_s != "default") ? "klods-badge--#{v}" : nil }
10
+ },
11
+ props: props, children: children
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ module Klods
2
+ module Components
3
+ module Box
4
+ def box(a = nil, b = nil)
5
+ props, children = Core.normalize_args(a, b)
6
+ Core.build(tag: "div", base: "klods-box", props: props, children: children)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,44 @@
1
+ module Klods
2
+ module Components
3
+ module Breadcrumb
4
+ def crumb(a = nil, b = nil)
5
+ props, children = Core.normalize_args(a, b)
6
+ href = props.delete(:href) || props.delete("href")
7
+ attrs = href ? props.merge("data-crumb-href" => href) : props
8
+ Core.el("li", attrs, children)
9
+ end
10
+
11
+ def breadcrumbs(crumbs, attrs = {})
12
+ items = crumbs.each_with_index.map do |crumb_node, i|
13
+ is_last = i == crumbs.length - 1
14
+ href = crumb_node.attrs["data-crumb-href"]
15
+ item_attrs = crumb_node.attrs.except("data-crumb-href")
16
+ extra_class = item_attrs.delete("class")
17
+
18
+ content = if href && !is_last
19
+ [Core.el("a", {"href" => href, "class" => "klods-breadcrumb__link"}, crumb_node.children)]
20
+ else
21
+ crumb_node.children
22
+ end
23
+
24
+ cls = Core.class_names("klods-breadcrumb__item", Core.resolve_class(extra_class))
25
+ final_attrs = item_attrs.merge("class" => cls.empty? ? nil : cls)
26
+ final_attrs["aria-current"] = "page" if is_last
27
+ final_attrs.compact!
28
+
29
+ Node.new("li", final_attrs, content)
30
+ end
31
+
32
+ attrs = attrs.transform_keys(&:to_s)
33
+ aria_label = attrs.delete("aria-label") || "Breadcrumb"
34
+ extra_class = attrs.delete("class")
35
+ cls = Core.resolve_class(extra_class)
36
+
37
+ nav_attrs = attrs.merge("aria-label" => aria_label)
38
+ nav_attrs["class"] = cls unless cls.empty?
39
+
40
+ Core.el("nav", nav_attrs, Core.el("ol", {"class" => "klods-breadcrumb__list"}, items))
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,22 @@
1
+ module Klods
2
+ module Components
3
+ module Button
4
+ def button(a = nil, b = nil)
5
+ props, children = Core.normalize_args(a, b)
6
+ merged = {"type" => "button"}.merge(props.transform_keys(&:to_s))
7
+ Core.build(
8
+ tag: "button", base: "klods-button",
9
+ modifiers: {
10
+ variant: ->(v) { (v && v.to_s != "default") ? "klods-button--#{v}" : nil }
11
+ },
12
+ props: merged, children: children
13
+ )
14
+ end
15
+
16
+ def button_group(a = nil, b = nil)
17
+ props, children = Core.normalize_args(a, b)
18
+ Core.build(tag: "div", base: "klods-button-group", props: props, children: children)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ module Klods
2
+ module Components
3
+ module Card
4
+ def card(a = nil, b = nil)
5
+ props, children = Core.normalize_args(a, b)
6
+ Core.build(
7
+ tag: "div", base: "klods-card",
8
+ modifiers: {elevated: "klods-card--elevated"},
9
+ props: props, children: children
10
+ )
11
+ end
12
+
13
+ def card_title(a = nil, b = nil)
14
+ props, children = Core.normalize_args(a, b)
15
+ Core.build(tag: "h3", base: "klods-card__title", props: props, children: children)
16
+ end
17
+
18
+ def card_body(a = nil, b = nil)
19
+ props, children = Core.normalize_args(a, b)
20
+ Core.build(tag: "div", base: "klods-card__body", props: props, children: children)
21
+ end
22
+
23
+ def card_footer(a = nil, b = nil)
24
+ props, children = Core.normalize_args(a, b)
25
+ Core.build(tag: "div", base: "klods-card__footer", props: props, children: children)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,38 @@
1
+ module Klods
2
+ module Components
3
+ module Code
4
+ def code_block(a = nil, b = nil)
5
+ props, content = Core.normalize_args(a, b)
6
+ props = props.transform_keys(&:to_s)
7
+ extra_class = props.delete("class")
8
+ cls = Core.class_names("klods-pre", Core.resolve_class(extra_class))
9
+ attrs = props.merge("class" => cls.empty? ? nil : cls).compact
10
+ Core.el("pre", attrs, Core.el("code", {}, content))
11
+ end
12
+
13
+ def inline_code(a = nil, b = nil)
14
+ props, content = Core.normalize_args(a, b)
15
+ props = props.transform_keys(&:to_s)
16
+ extra_class = props.delete("class")
17
+ cls = Core.class_names("klods-code", Core.resolve_class(extra_class))
18
+ attrs = props.merge("class" => cls.empty? ? nil : cls).compact
19
+ Core.el("code", attrs, content)
20
+ end
21
+
22
+ def kbd(a = nil, b = nil)
23
+ props, children = Core.normalize_args(a, b)
24
+ Core.build(tag: "kbd", base: "klods-kbd", props: props, children: children)
25
+ end
26
+
27
+ def samp(a = nil, b = nil)
28
+ props, children = Core.normalize_args(a, b)
29
+ Core.build(tag: "samp", base: "klods-samp", props: props, children: children)
30
+ end
31
+
32
+ def var_el(a = nil, b = nil)
33
+ props, children = Core.normalize_args(a, b)
34
+ Core.build(tag: "var", base: "klods-var", props: props, children: children)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,19 @@
1
+ module Klods
2
+ module Components
3
+ module Details
4
+ def details(a = nil, b = nil)
5
+ props, children = Core.normalize_args(a, b)
6
+ Core.build(
7
+ tag: "details", base: "klods-details",
8
+ modifiers: {open: "klods-details--open"},
9
+ props: props, children: children
10
+ )
11
+ end
12
+
13
+ def summary(a = nil, b = nil)
14
+ props, children = Core.normalize_args(a, b)
15
+ Core.el("summary", props, children)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ module Klods
2
+ module Components
3
+ module Dl
4
+ def dl(a = nil, b = nil)
5
+ props, children = Core.normalize_args(a, b)
6
+ Core.build(
7
+ tag: "dl", base: "klods-dl",
8
+ modifiers: {inline: "klods-dl--inline"},
9
+ props: props, children: children
10
+ )
11
+ end
12
+
13
+ def dt(a = nil, b = nil)
14
+ props, children = Core.normalize_args(a, b)
15
+ Core.build(tag: "dt", base: "klods-dt", props: props, children: children)
16
+ end
17
+
18
+ def dd(a = nil, b = nil)
19
+ props, children = Core.normalize_args(a, b)
20
+ Core.build(tag: "dd", base: "klods-dd", props: props, children: children)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,198 @@
1
+ module Klods
2
+ module Components
3
+ module Form
4
+ FORM_CONTROLS = %w[input select textarea].freeze
5
+ private_constant :FORM_CONTROLS
6
+
7
+ def form(a = nil, b = nil)
8
+ props, children = Core.normalize_args(a, b)
9
+ Core.build(tag: "form", base: "klods-form", props: props, children: children)
10
+ end
11
+
12
+ # field({ label: "Email", required: true }) { |id| input(id: id, type: "email") }
13
+ def field(props, &block)
14
+ props = props.transform_keys(&:to_s)
15
+ label_text = props.delete("label")
16
+ explicit_id = props.delete("id")
17
+ help = props.delete("help")
18
+ error = props.delete("error")
19
+ required = props.delete("required")
20
+ invalid = props.delete("invalid")
21
+ extra_class = props.delete("class")
22
+
23
+ id = explicit_id || Core.slug_id("klods-field", label_text.to_s)
24
+ help_id = help ? "#{id}-help" : nil
25
+ error_id = error ? "#{id}-error" : nil
26
+ is_invalid = invalid || !!error
27
+
28
+ described_by = is_invalid ? error_id : help_id
29
+
30
+ aria_attrs = {}
31
+ aria_attrs["aria-describedby"] = described_by if described_by
32
+ aria_attrs["aria-invalid"] = "true" if is_invalid
33
+
34
+ input_node = block.call(id)
35
+ patched = _patch_aria_attrs(input_node, aria_attrs)
36
+
37
+ field_cls = Core.class_names("klods-field", is_invalid ? "klods-field--invalid" : nil, Core.resolve_class(extra_class))
38
+ label_cls = "klods-label#{" klods-label--required" if required}"
39
+
40
+ children = [
41
+ Core.el("label", {"for" => id, "class" => label_cls}, label_text),
42
+ patched
43
+ ]
44
+ children << Core.el("p", {"id" => help_id, "class" => "klods-help"}, help) if help
45
+ children << Core.el("p", {"id" => error_id, "class" => "klods-error", "role" => "alert"}, error) if error
46
+
47
+ Core.el("div", props.merge("class" => field_cls.empty? ? nil : field_cls).compact, children)
48
+ end
49
+
50
+ def input(props = {})
51
+ props = props.transform_keys(&:to_s)
52
+ type = props.delete("type")
53
+ extra_class = props.delete("class")
54
+
55
+ id = props["id"] || Core.slug_id(
56
+ "klods-input",
57
+ (props["aria-label"] || props["placeholder"] || type || "field").to_s
58
+ )
59
+ props["id"] = id
60
+
61
+ cls = lambda do |extra = ""|
62
+ Core.class_names("klods-input", extra, Core.resolve_class(extra_class)).then { |c| c.empty? ? nil : c }
63
+ end
64
+
65
+ case type
66
+ when "range"
67
+ initial = props["value"] || "50"
68
+ Node.new("span", {"class" => cls.call("klods-input--range")}, [
69
+ Node.new("input", props.merge("type" => "range"), nil),
70
+ Core.el("output", {"for" => id}, initial.to_s)
71
+ ])
72
+ when "color"
73
+ initial = props["value"] || "#000000"
74
+ Node.new("span", {"class" => cls.call("klods-input--color")}, [
75
+ Node.new("input", props.merge("type" => "color"), nil),
76
+ Core.el("output", {"for" => id}, initial.to_s)
77
+ ])
78
+ else
79
+ Node.new("input", props.merge("type" => type, "class" => cls.call).compact)
80
+ end
81
+ end
82
+
83
+ def select(a = nil, b = nil)
84
+ props, children = Core.normalize_args(a, b)
85
+ inner = Core.build(tag: "select", base: "klods-select", props: props, children: children)
86
+ Core.el("div", {"class" => "klods-select-wrapper"}, inner)
87
+ end
88
+
89
+ def option(a = nil, b = nil)
90
+ props, children = Core.normalize_args(a, b)
91
+ Core.el("option", props, children)
92
+ end
93
+
94
+ def textarea(a = nil, b = nil)
95
+ props, children = Core.normalize_args(a, b)
96
+ Core.build(tag: "textarea", base: "klods-textarea", props: props, children: children)
97
+ end
98
+
99
+ def checkbox(props)
100
+ props = props.transform_keys(&:to_s)
101
+ label_text = props.delete("label")
102
+ extra_class = props.delete("class")
103
+
104
+ input_attrs = {"type" => "checkbox"}
105
+ %w[name value checked disabled required form autofocus].each do |key|
106
+ val = props.delete(key)
107
+ input_attrs[key] = val unless val.nil?
108
+ end
109
+
110
+ cls = Core.class_names("klods-checkbox", Core.resolve_class(extra_class))
111
+ Core.el(
112
+ "label",
113
+ props.merge("class" => cls.empty? ? nil : cls).compact,
114
+ [Node.new("input", input_attrs), Core.el("span", {}, label_text)]
115
+ )
116
+ end
117
+
118
+ def radio(props)
119
+ props = props.transform_keys(&:to_s)
120
+ label_text = props.delete("label")
121
+ extra_class = props.delete("class")
122
+
123
+ input_attrs = {"type" => "radio"}
124
+ %w[name value checked disabled required form autofocus].each do |key|
125
+ val = props.delete(key)
126
+ input_attrs[key] = val unless val.nil?
127
+ end
128
+
129
+ cls = Core.class_names("klods-radio", Core.resolve_class(extra_class))
130
+ Core.el(
131
+ "label",
132
+ props.merge("class" => cls.empty? ? nil : cls).compact,
133
+ [Node.new("input", input_attrs), Core.el("span", {}, label_text)]
134
+ )
135
+ end
136
+
137
+ def radio_group(props, children)
138
+ props = props.transform_keys(&:to_s)
139
+ legend_text = props.delete("legend")
140
+ extra_class = props.delete("class")
141
+ legend_id = legend_text ? Core.slug_id("klods-rg", legend_text) : nil
142
+
143
+ cls = Core.class_names("klods-field", Core.resolve_class(extra_class))
144
+ attrs = props.merge("role" => "group", "class" => cls.empty? ? nil : cls).compact
145
+ attrs["aria-labelledby"] = legend_id if legend_id
146
+
147
+ group_children = []
148
+ group_children << Core.el("p", {"id" => legend_id, "class" => "klods-label"}, legend_text) if legend_text
149
+ group_children.concat(Array(children))
150
+
151
+ Core.el("div", attrs, group_children)
152
+ end
153
+
154
+ def switch_input(props)
155
+ props = props.transform_keys(&:to_s)
156
+ label_text = props.delete("label")
157
+ reverse = props.delete("reverse")
158
+ extra_class = props.delete("class")
159
+
160
+ input_attrs = {"type" => "checkbox", "class" => "klods-switch__input", "role" => "switch"}
161
+ %w[name value checked disabled].each do |key|
162
+ val = props.delete(key)
163
+ input_attrs[key] = val unless val.nil?
164
+ end
165
+
166
+ cls = Core.class_names("klods-switch", reverse ? "klods-switch--reverse" : nil, Core.resolve_class(extra_class))
167
+ Core.el(
168
+ "label",
169
+ props.merge("class" => cls.empty? ? nil : cls).compact,
170
+ [
171
+ Node.new("input", input_attrs),
172
+ Core.el("span", {"class" => "klods-switch__track"}),
173
+ Core.el("span", {"class" => "klods-switch__label"}, label_text)
174
+ ]
175
+ )
176
+ end
177
+
178
+ private
179
+
180
+ def _patch_aria_attrs(node, aria_attrs)
181
+ return node if aria_attrs.empty?
182
+
183
+ if FORM_CONTROLS.include?(node.tag)
184
+ Node.new(node.tag, node.attrs.merge(aria_attrs), node.children)
185
+ else
186
+ patched_children = node.children.map do |child|
187
+ if child.is_a?(Node) && FORM_CONTROLS.include?(child.tag)
188
+ Node.new(child.tag, child.attrs.merge(aria_attrs), child.children)
189
+ else
190
+ child
191
+ end
192
+ end
193
+ Node.new(node.tag, node.attrs, patched_children)
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,50 @@
1
+ module Klods
2
+ module Components
3
+ module List
4
+ def list(a = nil, b = nil)
5
+ props, children = Core.normalize_args(a, b)
6
+ Core.build(
7
+ tag: "ul", base: "klods-list",
8
+ modifiers: {flush: "klods-list--flush"},
9
+ props: props, children: children
10
+ )
11
+ end
12
+
13
+ def list_item(a = nil, b = nil)
14
+ props, children = Core.normalize_args(a, b)
15
+ props = props.transform_keys(&:to_s)
16
+
17
+ lead = props.delete("lead")
18
+ trail = props.delete("trail")
19
+ href = props.delete("href")
20
+ extra_class = props.delete("class")
21
+
22
+ li_cls = Core.class_names(
23
+ "klods-list__item",
24
+ href ? "klods-list__item--link" : nil,
25
+ Core.resolve_class(extra_class)
26
+ )
27
+
28
+ has_slots = !lead.nil? || !trail.nil?
29
+
30
+ build_slots = lambda do
31
+ parts = []
32
+ parts << Core.el("span", {"class" => "klods-list__lead"}, lead) if lead
33
+ parts << Core.el("span", {"class" => "klods-list__content"}, children)
34
+ parts << Core.el("span", {"class" => "klods-list__trail"}, trail) if trail
35
+ parts
36
+ end
37
+
38
+ li_attrs = props.merge("class" => li_cls.empty? ? nil : li_cls).compact
39
+
40
+ if href
41
+ link_content = has_slots ? build_slots.call : children
42
+ return Core.el("li", li_attrs,
43
+ Core.el("a", {"href" => href, "class" => "klods-list__link"}, link_content))
44
+ end
45
+
46
+ Core.el("li", li_attrs, has_slots ? build_slots.call : children)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,52 @@
1
+ module Klods
2
+ module Components
3
+ module Modal
4
+ def modal(a = nil, b = nil)
5
+ props, children = Core.normalize_args(a, b)
6
+ props = props.transform_keys(&:to_s)
7
+ open_attr = props.delete("open")
8
+ extra_class = props.delete("class")
9
+ cls = Core.class_names("klods-modal", Core.resolve_class(extra_class))
10
+ attrs = props.merge("class" => cls.empty? ? nil : cls).compact
11
+ attrs["open"] = true if open_attr
12
+ Core.el("dialog", attrs, children)
13
+ end
14
+
15
+ def modal_panel(a = nil, b = nil)
16
+ props, children = Core.normalize_args(a, b)
17
+ Core.build(tag: "div", base: "klods-modal__panel", props: props, children: children)
18
+ end
19
+
20
+ def modal_header(a = nil, b = nil)
21
+ props, children = Core.normalize_args(a, b)
22
+ Core.build(tag: "div", base: "klods-modal__header", props: props, children: children)
23
+ end
24
+
25
+ def modal_title(a = nil, b = nil)
26
+ props, children = Core.normalize_args(a, b)
27
+ Core.build(tag: "h2", base: "klods-modal__title", props: props, children: children)
28
+ end
29
+
30
+ def modal_body(a = nil, b = nil)
31
+ props, children = Core.normalize_args(a, b)
32
+ Core.build(tag: "div", base: "klods-modal__body", props: props, children: children)
33
+ end
34
+
35
+ def modal_actions(a = nil, b = nil)
36
+ props, children = Core.normalize_args(a, b)
37
+ Core.build(tag: "div", base: "klods-modal__actions", props: props, children: children)
38
+ end
39
+
40
+ def modal_close(attrs = nil)
41
+ attrs = (attrs || {}).transform_keys(&:to_s)
42
+ extra_class = attrs.delete("class")
43
+ cls = Core.class_names("klods-modal__close", Core.resolve_class(extra_class))
44
+ final_attrs = {"type" => "button", "aria-label" => "Close"}
45
+ .merge(attrs)
46
+ .merge("class" => cls.empty? ? nil : cls)
47
+ .compact
48
+ Core.el("button", final_attrs)
49
+ end
50
+ end
51
+ end
52
+ end