klods-ruby 0.2.1 → 1.0.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 +4 -4
- data/lib/klods/components/modal.rb +41 -1
- data/lib/klods/components/tabs.rb +14 -1
- data/lib/klods/components/toast.rb +44 -1
- data/lib/klods/components/tooltip.rb +10 -3
- data/lib/klods/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 844cc92165c4952be948f23654939d6781ca0feda123c8ea10e8296e22b806a0
|
|
4
|
+
data.tar.gz: c4952d7416e7f9dbf522649725e1d28deaad0722d3ceea78899961e18186a4e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: df8ed9b618d7d6f9b3b9400a323e3128d70ef8ee1749ed687362c5640a5a8a3015127b6449b4d240b009496147179ac8ab15639d2dd01dbbc598ac565e20066d
|
|
7
|
+
data.tar.gz: b0ee9c030e8d944baa807a2c2971142f6b9a39889624233daba59c1caa34ec02f09f8dbec5ba7070a7b01a30b29799c6ef4a3d33d6078a5bab097fd09741c5a4
|
|
@@ -37,16 +37,56 @@ module Klods
|
|
|
37
37
|
Core.build(tag: "div", base: "klods-modal__actions", props: props, children: children)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Close button — the × icon is provided by CSS via a ::before mask-image.
|
|
41
|
+
# Closes the containing dialog automatically via inline JS.
|
|
40
42
|
def modal_close(attrs = nil)
|
|
41
43
|
attrs = (attrs || {}).transform_keys(&:to_s)
|
|
42
44
|
extra_class = attrs.delete("class")
|
|
43
45
|
cls = Core.class_names("klods-modal__close", Core.resolve_class(extra_class))
|
|
44
|
-
final_attrs = {
|
|
46
|
+
final_attrs = {
|
|
47
|
+
"type" => "button",
|
|
48
|
+
"aria-label" => "Close",
|
|
49
|
+
"onclick" => "this.closest('dialog').close()"
|
|
50
|
+
}
|
|
45
51
|
.merge(attrs)
|
|
46
52
|
.merge("class" => cls.empty? ? nil : cls)
|
|
47
53
|
.compact
|
|
48
54
|
Core.el("button", final_attrs)
|
|
49
55
|
end
|
|
56
|
+
|
|
57
|
+
# Button that closes the containing <dialog> when clicked.
|
|
58
|
+
# Accepts the same props as button (e.g. variant:).
|
|
59
|
+
def modal_dismiss(a = nil, b = nil)
|
|
60
|
+
props, children = Core.normalize_args(a, b)
|
|
61
|
+
merged = {
|
|
62
|
+
"type" => "button",
|
|
63
|
+
"onclick" => "this.closest('dialog').close()"
|
|
64
|
+
}.merge(props.transform_keys(&:to_s))
|
|
65
|
+
Core.build(
|
|
66
|
+
tag: "button", base: "klods-button",
|
|
67
|
+
modifiers: {
|
|
68
|
+
variant: ->(v) { (v && v.to_s != "default") ? "klods-button--#{v}" : nil }
|
|
69
|
+
},
|
|
70
|
+
props: merged, children: children
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Button that opens the next sibling <dialog> as a modal when clicked.
|
|
75
|
+
# Accepts the same props as button (e.g. variant:).
|
|
76
|
+
def modal_trigger(a = nil, b = nil)
|
|
77
|
+
props, children = Core.normalize_args(a, b)
|
|
78
|
+
merged = {
|
|
79
|
+
"type" => "button",
|
|
80
|
+
"onclick" => "this.nextElementSibling.showModal()"
|
|
81
|
+
}.merge(props.transform_keys(&:to_s))
|
|
82
|
+
Core.build(
|
|
83
|
+
tag: "button", base: "klods-button",
|
|
84
|
+
modifiers: {
|
|
85
|
+
variant: ->(v) { (v && v.to_s != "default") ? "klods-button--#{v}" : nil }
|
|
86
|
+
},
|
|
87
|
+
props: merged, children: children
|
|
88
|
+
)
|
|
89
|
+
end
|
|
50
90
|
end
|
|
51
91
|
end
|
|
52
92
|
end
|
|
@@ -22,6 +22,18 @@ module Klods
|
|
|
22
22
|
{panel: panel, label: label, tab_id: tab_id, panel_id: panel_id, active: i == 0}
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
activate_tab_js =
|
|
26
|
+
"var c=this.closest('.klods-tabs');" \
|
|
27
|
+
"c.querySelectorAll('[role=tab]').forEach(t=>{" \
|
|
28
|
+
"var a=t===this;" \
|
|
29
|
+
"t.setAttribute('aria-selected',a);" \
|
|
30
|
+
"t.classList.toggle('klods-tabs__tab--active',a);" \
|
|
31
|
+
"a?t.removeAttribute('tabindex'):t.setAttribute('tabindex','-1')" \
|
|
32
|
+
"});" \
|
|
33
|
+
"c.querySelectorAll(':scope>.klods-tabs__panel').forEach(p=>{" \
|
|
34
|
+
"p.hidden=p.id!==this.getAttribute('aria-controls')" \
|
|
35
|
+
"})"
|
|
36
|
+
|
|
25
37
|
tab_list = Core.el(
|
|
26
38
|
"div",
|
|
27
39
|
{"class" => "klods-tabs__list", "role" => "tablist"},
|
|
@@ -32,7 +44,8 @@ module Klods
|
|
|
32
44
|
"id" => item[:tab_id],
|
|
33
45
|
"aria-selected" => item[:active].to_s,
|
|
34
46
|
"aria-controls" => item[:panel_id],
|
|
35
|
-
"class" => item[:active] ? "klods-tabs__tab klods-tabs__tab--active" : "klods-tabs__tab"
|
|
47
|
+
"class" => item[:active] ? "klods-tabs__tab klods-tabs__tab--active" : "klods-tabs__tab",
|
|
48
|
+
"onclick" => activate_tab_js
|
|
36
49
|
}
|
|
37
50
|
tab_attrs["tabindex"] = "-1" unless item[:active]
|
|
38
51
|
Core.el("button", tab_attrs, item[:label])
|
|
@@ -28,9 +28,52 @@ module Klods
|
|
|
28
28
|
)
|
|
29
29
|
attrs = props.merge("role" => "status", "class" => cls.empty? ? nil : cls).compact
|
|
30
30
|
body = Core.el("span", {"class" => "klods-toast__body"}, children)
|
|
31
|
-
|
|
31
|
+
dismiss_js = "var e=this.closest('.klods-toast');if(e){e.setAttribute('data-dismissing','');var f=setTimeout(function(){e.remove()},200);e.addEventListener('animationend',function(){clearTimeout(f);e.remove()},{once:true})}"
|
|
32
|
+
close_btn = Core.el("button", {"type" => "button", "class" => "klods-toast__close", "aria-label" => "Dismiss", "onclick" => dismiss_js})
|
|
32
33
|
Core.el("div", attrs, [body, close_btn])
|
|
33
34
|
end
|
|
35
|
+
|
|
36
|
+
# Renders a button that dynamically creates and shows a toast on click.
|
|
37
|
+
# message: the toast body text (string). toast_variant: toast style. duration: ms (0 = persistent).
|
|
38
|
+
def toast_trigger(a = nil, b = nil)
|
|
39
|
+
props, children = Core.normalize_args(a, b)
|
|
40
|
+
props = props.transform_keys(&:to_s)
|
|
41
|
+
message = props.delete("message")
|
|
42
|
+
message = children if message.nil?
|
|
43
|
+
variant = props.delete("toast_variant") || "default"
|
|
44
|
+
duration = props.key?("duration") ? props.delete("duration").to_i : 5000
|
|
45
|
+
|
|
46
|
+
escaped = message.to_s.gsub(/[\\']/) { |c| "\\#{c}" }
|
|
47
|
+
toast_class = (variant.to_s == "default") ? "klods-toast" : "klods-toast klods-toast--#{variant}"
|
|
48
|
+
|
|
49
|
+
dismiss_fn = "function _kd(e){e.setAttribute('data-dismissing','');var f=setTimeout(function(){e.remove()},200);e.addEventListener('animationend',function(){clearTimeout(f);e.remove()},{once:true})}"
|
|
50
|
+
get_region = "var r=document.querySelector('.klods-toast-region');" \
|
|
51
|
+
"if(!r){r=document.createElement('div');r.className='klods-toast-region';" \
|
|
52
|
+
"r.setAttribute('aria-live','polite');r.setAttribute('aria-atomic','false');" \
|
|
53
|
+
"r.setAttribute('role','region');r.setAttribute('aria-label','Notifications');" \
|
|
54
|
+
"document.body.appendChild(r)}"
|
|
55
|
+
make_toast = "var t=document.createElement('div');t.className='#{toast_class}';t.setAttribute('role','status');" \
|
|
56
|
+
"var b=document.createElement('span');b.className='klods-toast__body';b.textContent='#{escaped}';t.appendChild(b);" \
|
|
57
|
+
"var c=document.createElement('button');c.type='button';c.className='klods-toast__close';" \
|
|
58
|
+
"c.setAttribute('aria-label','Dismiss');c.onclick=function(){_kd(t)};t.appendChild(c);r.appendChild(t)"
|
|
59
|
+
auto_dismiss = (duration > 0) ? ";setTimeout(function(){_kd(t)},#{duration})" : ""
|
|
60
|
+
onclick = "(function(){#{dismiss_fn};#{get_region};#{make_toast}#{auto_dismiss}})()"
|
|
61
|
+
|
|
62
|
+
Core.build(tag: "button", base: "klods-button",
|
|
63
|
+
modifiers: {variant: ->(v) { (v && v.to_s != "default") ? "klods-button--#{v}" : nil }},
|
|
64
|
+
props: props.merge("type" => "button", "onclick" => onclick),
|
|
65
|
+
children: children)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Renders a button that removes all visible toasts on click.
|
|
69
|
+
def clear_toasts_trigger(a = nil, b = nil)
|
|
70
|
+
props, children = Core.normalize_args(a, b)
|
|
71
|
+
onclick = "document.querySelectorAll('.klods-toast-region').forEach(function(r){r.remove()})"
|
|
72
|
+
Core.build(tag: "button", base: "klods-button",
|
|
73
|
+
modifiers: {variant: ->(v) { (v && v.to_s != "default") ? "klods-button--#{v}" : nil }},
|
|
74
|
+
props: props.transform_keys(&:to_s).merge("type" => "button", "onclick" => onclick),
|
|
75
|
+
children: children)
|
|
76
|
+
end
|
|
34
77
|
end
|
|
35
78
|
end
|
|
36
79
|
end
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
module Klods
|
|
2
2
|
module Components
|
|
3
3
|
module Tooltip
|
|
4
|
-
# tooltip(props, children) — renders the static HTML structure.
|
|
5
|
-
# Visibility is toggled by data-open via client JS; event wiring is omitted here.
|
|
6
4
|
def tooltip(props, children)
|
|
7
5
|
props = props.transform_keys(&:to_s)
|
|
8
6
|
tip = props.delete("tip")
|
|
@@ -16,10 +14,19 @@ module Klods
|
|
|
16
14
|
tip
|
|
17
15
|
)
|
|
18
16
|
|
|
17
|
+
# Inline JS mirrors showTooltip/hideTooltip from klods-js.
|
|
18
|
+
# _kh (klods hide) is stored on the tip element to cancel pending hides.
|
|
19
|
+
show_js = "var t=this.querySelector('[role=tooltip]');if(t){clearTimeout(t._kh);t.setAttribute('data-open','')}"
|
|
20
|
+
hide_js = "var t=this.querySelector('[role=tooltip]');if(t){t._kh=setTimeout(()=>t.removeAttribute('data-open'),80)}"
|
|
21
|
+
|
|
19
22
|
cls = Core.class_names("klods-tooltip", Core.resolve_class(extra_class))
|
|
20
23
|
attrs = props.merge(
|
|
21
24
|
"class" => cls.empty? ? nil : cls,
|
|
22
|
-
"aria-describedby" => id
|
|
25
|
+
"aria-describedby" => id,
|
|
26
|
+
"onmouseenter" => show_js,
|
|
27
|
+
"onmouseleave" => hide_js,
|
|
28
|
+
"onfocusin" => show_js,
|
|
29
|
+
"onfocusout" => "if(!this.contains(event.relatedTarget)){var t=this.querySelector('[role=tooltip]');if(t)t.removeAttribute('data-open')}"
|
|
23
30
|
).compact
|
|
24
31
|
|
|
25
32
|
Core.el("span", attrs, [children, tip_node])
|
data/lib/klods/version.rb
CHANGED