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 +7 -0
- data/lib/klods/builders.rb +35 -0
- data/lib/klods/components/alert.rb +17 -0
- data/lib/klods/components/avatar.rb +46 -0
- data/lib/klods/components/badge.rb +16 -0
- data/lib/klods/components/box.rb +10 -0
- data/lib/klods/components/breadcrumb.rb +44 -0
- data/lib/klods/components/button.rb +22 -0
- data/lib/klods/components/card.rb +29 -0
- data/lib/klods/components/code.rb +38 -0
- data/lib/klods/components/details.rb +19 -0
- data/lib/klods/components/dl.rb +24 -0
- data/lib/klods/components/form.rb +198 -0
- data/lib/klods/components/list.rb +50 -0
- data/lib/klods/components/modal.rb +52 -0
- data/lib/klods/components/nav.rb +64 -0
- data/lib/klods/components/prose.rb +25 -0
- data/lib/klods/components/table.rb +47 -0
- data/lib/klods/components/tabs.rb +63 -0
- data/lib/klods/components/toast.rb +36 -0
- data/lib/klods/components/tooltip.rb +36 -0
- data/lib/klods/components.rb +19 -0
- data/lib/klods/core.rb +92 -0
- data/lib/klods/html.rb +23 -0
- data/lib/klods/icons.rb +120 -0
- data/lib/klods/layout.rb +110 -0
- data/lib/klods/node.rb +98 -0
- data/lib/klods/rails_safety.rb +9 -0
- data/lib/klods/railtie.rb +10 -0
- data/lib/klods/utilities.rb +13 -0
- data/lib/klods/version.rb +3 -0
- data/lib/klods.rb +11 -0
- metadata +103 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Klods
|
|
2
|
+
module Components
|
|
3
|
+
module Nav
|
|
4
|
+
MENU_SVG = '<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">' \
|
|
5
|
+
'<rect y="3" width="20" height="2" rx="1" fill="currentColor"/>' \
|
|
6
|
+
'<rect y="9" width="20" height="2" rx="1" fill="currentColor"/>' \
|
|
7
|
+
'<rect y="15" width="20" height="2" rx="1" fill="currentColor"/>' \
|
|
8
|
+
"</svg>"
|
|
9
|
+
|
|
10
|
+
private_constant :MENU_SVG
|
|
11
|
+
|
|
12
|
+
def nav(a = nil, b = nil)
|
|
13
|
+
props, children = Core.normalize_args(a, b)
|
|
14
|
+
Core.build(
|
|
15
|
+
tag: "nav", base: "klods-nav",
|
|
16
|
+
modifiers: {collapse: "klods-nav--collapse"},
|
|
17
|
+
props: props, children: children
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def nav_list(a = nil, b = nil)
|
|
22
|
+
props, children = Core.normalize_args(a, b)
|
|
23
|
+
Core.build(tag: "ul", base: "klods-nav__list", props: props, children: children)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def nav_link(a = nil, b = nil)
|
|
27
|
+
props, children = Core.normalize_args(a, b)
|
|
28
|
+
link = Core.build(
|
|
29
|
+
tag: "a", base: "klods-nav__link",
|
|
30
|
+
modifiers: {active: "klods-nav__link--active"},
|
|
31
|
+
props: props, children: children
|
|
32
|
+
)
|
|
33
|
+
Core.el("li", {}, link)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def nav_toggle(a = nil, b = nil)
|
|
37
|
+
props, children = Core.normalize_args(a, b)
|
|
38
|
+
props = {"type" => "button", "aria-label" => "Toggle navigation", "class" => "klods-nav__toggle"}
|
|
39
|
+
.merge(props.transform_keys(&:to_s))
|
|
40
|
+
default_icon = Core.el("span", {"aria-hidden" => "true", "class" => "klods-icon"}, Core.raw(MENU_SVG))
|
|
41
|
+
Core.el("button", props, children || default_icon)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def toc(a = nil, b = nil)
|
|
45
|
+
props, children = Core.normalize_args(a, b)
|
|
46
|
+
Core.build(
|
|
47
|
+
tag: "ul", base: "klods-toc",
|
|
48
|
+
modifiers: {sub: "klods-toc--sub"},
|
|
49
|
+
props: props, children: children
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def toc_item(a = nil, b = nil)
|
|
54
|
+
props, children = Core.normalize_args(a, b)
|
|
55
|
+
Core.el("li", props, children)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def toc_link(a = nil, b = nil)
|
|
59
|
+
props, children = Core.normalize_args(a, b)
|
|
60
|
+
Core.el("a", props, children)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Klods
|
|
2
|
+
module Components
|
|
3
|
+
module Prose
|
|
4
|
+
def prose(a = nil, b = nil)
|
|
5
|
+
props, children = Core.normalize_args(a, b)
|
|
6
|
+
Core.build(tag: "div", base: "klods-prose", props: props, children: children)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def muted(a = nil, b = nil)
|
|
10
|
+
props, children = Core.normalize_args(a, b)
|
|
11
|
+
Core.build(tag: "span", base: "klods-muted", props: props, children: children)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def lead(a = nil, b = nil)
|
|
15
|
+
props, children = Core.normalize_args(a, b)
|
|
16
|
+
Core.build(tag: "p", base: "klods-lead", props: props, children: children)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def text_center(a = nil, b = nil)
|
|
20
|
+
props, children = Core.normalize_args(a, b)
|
|
21
|
+
Core.build(tag: "div", base: "klods-text-center", props: props, children: children)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Klods
|
|
2
|
+
module Components
|
|
3
|
+
module Table
|
|
4
|
+
def table_wrap(a = nil, b = nil)
|
|
5
|
+
props, children = Core.normalize_args(a, b)
|
|
6
|
+
Core.build(tag: "div", base: "klods-table-wrap", props: props, children: children)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def table(a = nil, b = nil)
|
|
10
|
+
props, children = Core.normalize_args(a, b)
|
|
11
|
+
Core.build(
|
|
12
|
+
tag: "table", base: "klods-table",
|
|
13
|
+
modifiers: {
|
|
14
|
+
striped: "klods-table--striped",
|
|
15
|
+
dense: "klods-table--dense"
|
|
16
|
+
},
|
|
17
|
+
props: props, children: children
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def thead(a = nil, b = nil)
|
|
22
|
+
props, children = Core.normalize_args(a, b)
|
|
23
|
+
Core.el("thead", props, children)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def tbody(a = nil, b = nil)
|
|
27
|
+
props, children = Core.normalize_args(a, b)
|
|
28
|
+
Core.el("tbody", props, children)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def tr(a = nil, b = nil)
|
|
32
|
+
props, children = Core.normalize_args(a, b)
|
|
33
|
+
Core.el("tr", props, children)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def th(a = nil, b = nil)
|
|
37
|
+
props, children = Core.normalize_args(a, b)
|
|
38
|
+
Core.el("th", props, children)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def td(a = nil, b = nil)
|
|
42
|
+
props, children = Core.normalize_args(a, b)
|
|
43
|
+
Core.el("td", props, children)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Klods
|
|
2
|
+
module Components
|
|
3
|
+
module Tabs
|
|
4
|
+
def tab_panel(a = nil, b = nil)
|
|
5
|
+
props, children = Core.normalize_args(a, b)
|
|
6
|
+
label = props.delete(:label) || props.delete("label")
|
|
7
|
+
attrs = label ? props.merge("data-tab-label" => label.to_s) : props
|
|
8
|
+
Core.el("div", attrs, children)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def tabs(a = nil, b = nil)
|
|
12
|
+
props, children = Core.normalize_args(a, b)
|
|
13
|
+
props = props.transform_keys(&:to_s)
|
|
14
|
+
panels = Array(children).flatten.select { |c| c.is_a?(Node) }
|
|
15
|
+
|
|
16
|
+
ns = props["id"] ? Core.slug_id("klods-tabs", props["id"].to_s) : "klods-tabs"
|
|
17
|
+
|
|
18
|
+
items = panels.each_with_index.map do |panel, i|
|
|
19
|
+
label = panel.attrs["data-tab-label"] || "Tab #{i + 1}"
|
|
20
|
+
tab_id = Core.slug_id("#{ns}-tab", "#{label}-#{i + 1}")
|
|
21
|
+
panel_id = Core.slug_id("#{ns}-panel", "#{label}-#{i + 1}")
|
|
22
|
+
{panel: panel, label: label, tab_id: tab_id, panel_id: panel_id, active: i == 0}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
tab_list = Core.el(
|
|
26
|
+
"div",
|
|
27
|
+
{"class" => "klods-tabs__list", "role" => "tablist"},
|
|
28
|
+
items.map do |item|
|
|
29
|
+
tab_attrs = {
|
|
30
|
+
"type" => "button",
|
|
31
|
+
"role" => "tab",
|
|
32
|
+
"id" => item[:tab_id],
|
|
33
|
+
"aria-selected" => item[:active].to_s,
|
|
34
|
+
"aria-controls" => item[:panel_id],
|
|
35
|
+
"class" => item[:active] ? "klods-tabs__tab klods-tabs__tab--active" : "klods-tabs__tab"
|
|
36
|
+
}
|
|
37
|
+
tab_attrs["tabindex"] = "-1" unless item[:active]
|
|
38
|
+
Core.el("button", tab_attrs, item[:label])
|
|
39
|
+
end
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
panel_nodes = items.map do |item|
|
|
43
|
+
panel = item[:panel]
|
|
44
|
+
panel_attrs = panel.attrs.except("data-tab-label")
|
|
45
|
+
extra_class = panel_attrs.delete("class")
|
|
46
|
+
cls = Core.class_names("klods-tabs__panel", Core.resolve_class(extra_class))
|
|
47
|
+
attrs = panel_attrs.merge(
|
|
48
|
+
"class" => cls.empty? ? nil : cls,
|
|
49
|
+
"role" => "tabpanel",
|
|
50
|
+
"id" => item[:panel_id],
|
|
51
|
+
"aria-labelledby" => item[:tab_id]
|
|
52
|
+
).compact
|
|
53
|
+
attrs["hidden"] = true unless item[:active]
|
|
54
|
+
Node.new("div", attrs, panel.children)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
extra_class = props.delete("class")
|
|
58
|
+
cls = Core.class_names("klods-tabs", Core.resolve_class(extra_class))
|
|
59
|
+
Core.el("div", props.merge("class" => cls.empty? ? nil : cls).compact, [tab_list, *panel_nodes])
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Klods
|
|
2
|
+
module Components
|
|
3
|
+
module Toast
|
|
4
|
+
# Renders the live region container. Mount this once in your layout;
|
|
5
|
+
# individual toasts are appended inside it (via JS on the client, or
|
|
6
|
+
# server-rendered for SSR pre-populated notifications).
|
|
7
|
+
def toast_region(a = nil, b = nil)
|
|
8
|
+
props, children = Core.normalize_args(a, b)
|
|
9
|
+
attrs = {
|
|
10
|
+
"class" => "klods-toast-region",
|
|
11
|
+
"aria-live" => "polite",
|
|
12
|
+
"aria-atomic" => "false",
|
|
13
|
+
"role" => "region",
|
|
14
|
+
"aria-label" => "Notifications"
|
|
15
|
+
}.merge(props.transform_keys(&:to_s))
|
|
16
|
+
Core.el("div", attrs, children)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def toast(a = nil, b = nil)
|
|
20
|
+
props, children = Core.normalize_args(a, b)
|
|
21
|
+
props = props.transform_keys(&:to_s)
|
|
22
|
+
variant = props.delete("variant")
|
|
23
|
+
extra_class = props.delete("class")
|
|
24
|
+
cls = Core.class_names(
|
|
25
|
+
"klods-toast",
|
|
26
|
+
(variant && variant.to_s != "default") ? "klods-toast--#{variant}" : nil,
|
|
27
|
+
Core.resolve_class(extra_class)
|
|
28
|
+
)
|
|
29
|
+
attrs = props.merge("role" => "status", "class" => cls.empty? ? nil : cls).compact
|
|
30
|
+
body = Core.el("span", {"class" => "klods-toast__body"}, children)
|
|
31
|
+
close_btn = Core.el("button", {"type" => "button", "class" => "klods-toast__close", "aria-label" => "Dismiss"})
|
|
32
|
+
Core.el("div", attrs, [body, close_btn])
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Klods
|
|
2
|
+
module Components
|
|
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
|
+
def tooltip(props, children)
|
|
7
|
+
props = props.transform_keys(&:to_s)
|
|
8
|
+
tip = props.delete("tip")
|
|
9
|
+
position = props.delete("position") || "above"
|
|
10
|
+
extra_class = props.delete("class")
|
|
11
|
+
|
|
12
|
+
id = _next_tooltip_id
|
|
13
|
+
tip_node = Core.el(
|
|
14
|
+
"span",
|
|
15
|
+
{"id" => id, "role" => "tooltip", "class" => "klods-tooltip__tip klods-tooltip__tip--#{position}"},
|
|
16
|
+
tip
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
cls = Core.class_names("klods-tooltip", Core.resolve_class(extra_class))
|
|
20
|
+
attrs = props.merge(
|
|
21
|
+
"class" => cls.empty? ? nil : cls,
|
|
22
|
+
"aria-describedby" => id
|
|
23
|
+
).compact
|
|
24
|
+
|
|
25
|
+
Core.el("span", attrs, [children, tip_node])
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def _next_tooltip_id
|
|
31
|
+
Thread.current[:klods_tooltip_counter] = (Thread.current[:klods_tooltip_counter] || 0) + 1
|
|
32
|
+
"klods-tip-#{Thread.current[:klods_tooltip_counter]}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative "components/alert"
|
|
2
|
+
require_relative "components/avatar"
|
|
3
|
+
require_relative "components/badge"
|
|
4
|
+
require_relative "components/box"
|
|
5
|
+
require_relative "components/breadcrumb"
|
|
6
|
+
require_relative "components/button"
|
|
7
|
+
require_relative "components/card"
|
|
8
|
+
require_relative "components/code"
|
|
9
|
+
require_relative "components/details"
|
|
10
|
+
require_relative "components/dl"
|
|
11
|
+
require_relative "components/form"
|
|
12
|
+
require_relative "components/list"
|
|
13
|
+
require_relative "components/modal"
|
|
14
|
+
require_relative "components/nav"
|
|
15
|
+
require_relative "components/prose"
|
|
16
|
+
require_relative "components/table"
|
|
17
|
+
require_relative "components/tabs"
|
|
18
|
+
require_relative "components/toast"
|
|
19
|
+
require_relative "components/tooltip"
|
data/lib/klods/core.rb
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module Klods
|
|
2
|
+
module Core
|
|
3
|
+
module_function
|
|
4
|
+
|
|
5
|
+
def raw(html)
|
|
6
|
+
RawHtml.new(html)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def resolve_class(input)
|
|
10
|
+
case input
|
|
11
|
+
when nil then ""
|
|
12
|
+
when String then input.strip
|
|
13
|
+
when Array then input.flatten.filter_map { |c| resolve_class(c) unless resolve_class(c).empty? }.join(" ")
|
|
14
|
+
when Hash then input.filter_map { |k, v| k.to_s if v }.join(" ")
|
|
15
|
+
else input.to_s.strip
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def merge_classes(*inputs)
|
|
20
|
+
inputs.map { |c| resolve_class(c) }.reject(&:empty?).join(" ")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def class_names(*parts)
|
|
24
|
+
parts.flatten.compact.map(&:to_s).reject(&:empty?).join(" ")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Splits (props_or_children, children?) into [props_hash, children].
|
|
28
|
+
# A Hash first arg is props; anything else (string, array, Node, nil) is children.
|
|
29
|
+
def normalize_args(a = nil, b = nil)
|
|
30
|
+
if a.is_a?(Hash) || a.nil?
|
|
31
|
+
[a || {}, b]
|
|
32
|
+
else
|
|
33
|
+
[{}, a]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def el(tag, a = nil, b = nil)
|
|
38
|
+
props, children = normalize_args(a, b)
|
|
39
|
+
Node.new(tag, props, children)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# BEM builder factory — resolves modifier props into BEM classes, passes
|
|
43
|
+
# everything else through as HTML attributes.
|
|
44
|
+
def build(tag:, base:, modifiers: {}, props: {}, children: nil)
|
|
45
|
+
props = props.dup
|
|
46
|
+
|
|
47
|
+
resolved_children = if children.nil?
|
|
48
|
+
props.delete(:children) || props.delete("children")
|
|
49
|
+
else
|
|
50
|
+
props.delete(:children)
|
|
51
|
+
props.delete("children")
|
|
52
|
+
children
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
mod_classes = []
|
|
56
|
+
passthrough = {}
|
|
57
|
+
|
|
58
|
+
props.each do |key, value|
|
|
59
|
+
sym = key.is_a?(Symbol) ? key : key.to_s.to_sym
|
|
60
|
+
mod = modifiers[sym] || modifiers[key.to_s]
|
|
61
|
+
if mod
|
|
62
|
+
if mod.respond_to?(:call)
|
|
63
|
+
cls = mod.call(value)
|
|
64
|
+
mod_classes << cls if cls
|
|
65
|
+
elsif value
|
|
66
|
+
mod_classes << mod
|
|
67
|
+
end
|
|
68
|
+
else
|
|
69
|
+
passthrough[key] = value
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
user_class = passthrough.delete(:class) || passthrough.delete("class")
|
|
74
|
+
final_class = merge_classes(base, *mod_classes, user_class)
|
|
75
|
+
|
|
76
|
+
attrs = passthrough
|
|
77
|
+
attrs["class"] = final_class unless final_class.empty?
|
|
78
|
+
|
|
79
|
+
Node.new(tag, attrs, resolved_children)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def slug_id(prefix, text)
|
|
83
|
+
safe = text.to_s
|
|
84
|
+
slug = safe.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/^-|-$/, "")
|
|
85
|
+
return "#{prefix}-#{slug}" unless slug.empty?
|
|
86
|
+
|
|
87
|
+
h = 5381
|
|
88
|
+
safe.each_char { |c| h = ((h << 5) + h + c.ord) & 0xffffffff }
|
|
89
|
+
"#{prefix}-#{h.to_s(36)}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/klods/html.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Klods
|
|
2
|
+
# Raw HTML tag shortcuts — no BEM classes, just the element.
|
|
3
|
+
# Mirrors html.ts in klods-js. Tags that share a name with a klods component
|
|
4
|
+
# (nav, button, form, header, footer, section, table, thead, tbody, tr, th,
|
|
5
|
+
# td, input, select, textarea, details, summary, dl, dt, dd) are intentionally
|
|
6
|
+
# omitted — use the klods component or Core.el("tag", ...) for the bare element.
|
|
7
|
+
module Html
|
|
8
|
+
%w[a abbr address article b blockquote br caption cite code col colgroup
|
|
9
|
+
data dfn div em figcaption figure h1 h2 h3 h4 h5 h6 hr i img ins
|
|
10
|
+
legend li mark ol p pre q s small span strong sub sup time u ul].each do |tag|
|
|
11
|
+
define_method(tag) do |a = nil, b = nil|
|
|
12
|
+
props, children = Core.normalize_args(a, b)
|
|
13
|
+
Core.el(tag, props, children)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# `var` is a reserved word in some linters; named var_el to match klods-js.
|
|
18
|
+
def var_el(a = nil, b = nil)
|
|
19
|
+
props, children = Core.normalize_args(a, b)
|
|
20
|
+
Core.el("var", props, children)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/klods/icons.rb
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
module Klods
|
|
2
|
+
module Icons
|
|
3
|
+
SIZE_PX = {"small" => 12, "medium" => 20, "large" => 32}.freeze
|
|
4
|
+
|
|
5
|
+
private_constant :SIZE_PX
|
|
6
|
+
|
|
7
|
+
def self._make_icon(svg_inner, view_box)
|
|
8
|
+
lambda do |props = nil|
|
|
9
|
+
props = props ? props.dup : {}
|
|
10
|
+
size = (props.delete(:size) || props.delete("size") || "medium").to_s
|
|
11
|
+
label = props.delete(:label) || props.delete("label")
|
|
12
|
+
extra_class = props.delete(:class) || props.delete("class")
|
|
13
|
+
px = SIZE_PX[size] || 20
|
|
14
|
+
aria = label ? {"aria-label" => label.to_s, "role" => "img"} : {"aria-hidden" => "true"}
|
|
15
|
+
cls = Core.class_names("klods-icon", Core.resolve_class(extra_class))
|
|
16
|
+
attrs = props.transform_keys(&:to_s).merge(aria)
|
|
17
|
+
attrs["class"] = cls unless cls.empty?
|
|
18
|
+
svg = %(<svg width="#{px}" height="#{px}" viewBox="#{view_box}" fill="none" xmlns="http://www.w3.org/2000/svg">#{svg_inner}</svg>)
|
|
19
|
+
Core.el("span", attrs, Core.raw(svg))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
CHECK_CIRCLE_ICON = _make_icon(
|
|
24
|
+
'<circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.5"/><path stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" d="M5 8l2 2.5 4-4"/>',
|
|
25
|
+
"0 0 16 16"
|
|
26
|
+
)
|
|
27
|
+
CHEV_DOWN_ICON = _make_icon(
|
|
28
|
+
'<path stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M2 5l6 6 6-6"/>',
|
|
29
|
+
"0 0 16 16"
|
|
30
|
+
)
|
|
31
|
+
CHEV_LEFT_ICON = _make_icon(
|
|
32
|
+
'<path stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M11 2l-6 6 6 6"/>',
|
|
33
|
+
"0 0 16 16"
|
|
34
|
+
)
|
|
35
|
+
CHEV_RIGHT_ICON = _make_icon(
|
|
36
|
+
'<path stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M5 2l6 6-6 6"/>',
|
|
37
|
+
"0 0 16 16"
|
|
38
|
+
)
|
|
39
|
+
CHEV_UP_ICON = _make_icon(
|
|
40
|
+
'<path stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M2 11l6-6 6 6"/>',
|
|
41
|
+
"0 0 16 16"
|
|
42
|
+
)
|
|
43
|
+
CLOSE_ICON = _make_icon(
|
|
44
|
+
'<path stroke="currentColor" stroke-linecap="round" stroke-width="1.75" d="M4 4l8 8M12 4L4 12"/>',
|
|
45
|
+
"0 0 16 16"
|
|
46
|
+
)
|
|
47
|
+
COPY_ICON = _make_icon(
|
|
48
|
+
'<rect x="5" y="5" width="8" height="8" rx="1.5" stroke="currentColor" stroke-width="1.5"/><path stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M11 5V3.5A1.5 1.5 0 009.5 2h-6A1.5 1.5 0 002 3.5v6A1.5 1.5 0 003.5 11H5"/>',
|
|
49
|
+
"0 0 16 16"
|
|
50
|
+
)
|
|
51
|
+
DANGER_CIRCLE_ICON = _make_icon(
|
|
52
|
+
'<circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.5"/><path stroke="currentColor" stroke-width="1.5" stroke-linecap="round" d="M5.5 5.5l5 5M10.5 5.5l-5 5"/>',
|
|
53
|
+
"0 0 16 16"
|
|
54
|
+
)
|
|
55
|
+
EDIT_ICON = _make_icon(
|
|
56
|
+
'<path stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M11.3 2a2 2 0 112.8 2.8L5 13.8l-3.5 1 1-3.5L11.3 2z"/>',
|
|
57
|
+
"0 0 16 16"
|
|
58
|
+
)
|
|
59
|
+
EXTERNAL_LINK_ICON = _make_icon(
|
|
60
|
+
'<path stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M9 3h4v4M13 3L7 9"/><path stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M6 4H4a1 1 0 00-1 1v7a1 1 0 001 1h7a1 1 0 001-1v-2"/>',
|
|
61
|
+
"0 0 16 16"
|
|
62
|
+
)
|
|
63
|
+
EYE_ICON = _make_icon(
|
|
64
|
+
'<path stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M1 8s2.5-5 7-5 7 5 7 5-2.5 5-7 5-7-5-7-5z"/><circle cx="8" cy="8" r="2" stroke="currentColor" stroke-width="1.5"/>',
|
|
65
|
+
"0 0 16 16"
|
|
66
|
+
)
|
|
67
|
+
EYE_OFF_ICON = _make_icon(
|
|
68
|
+
'<path stroke="currentColor" stroke-width="1.5" stroke-linecap="round" d="M2 2l12 12"/><path stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M4 5.3C2.4 6.5 1 8 1 8s2.5 5 7 5c1.4 0 2.7-.4 3.8-1M9 3.3C11.6 4 14.2 6.2 15 8a12 12 0 01-2 2.7"/><path stroke="currentColor" stroke-width="1.5" stroke-linecap="round" d="M9.8 9.8A2 2 0 016.2 6.2"/>',
|
|
69
|
+
"0 0 16 16"
|
|
70
|
+
)
|
|
71
|
+
INFO_CIRCLE_ICON = _make_icon(
|
|
72
|
+
'<circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.5"/><path stroke="currentColor" stroke-width="1" stroke-linecap="round" d="M8 7.5v3.5"/><circle cx="8" cy="5.5" r="0.75" fill="currentColor"/>',
|
|
73
|
+
"0 0 16 16"
|
|
74
|
+
)
|
|
75
|
+
MENU_ICON = _make_icon(
|
|
76
|
+
'<rect y="3" width="20" height="2" rx="1" fill="currentColor"/><rect y="9" width="20" height="2" rx="1" fill="currentColor"/><rect y="15" width="20" height="2" rx="1" fill="currentColor"/>',
|
|
77
|
+
"0 0 20 20"
|
|
78
|
+
)
|
|
79
|
+
PLUS_ICON = _make_icon(
|
|
80
|
+
'<path stroke="currentColor" stroke-width="2" stroke-linecap="round" d="M8 3v10M3 8h10"/>',
|
|
81
|
+
"0 0 16 16"
|
|
82
|
+
)
|
|
83
|
+
SEARCH_ICON = _make_icon(
|
|
84
|
+
'<circle cx="7" cy="7" r="4.5" stroke="currentColor" stroke-width="1.5"/><path stroke="currentColor" stroke-width="1.75" stroke-linecap="round" d="M10.5 10.5L14 14"/>',
|
|
85
|
+
"0 0 16 16"
|
|
86
|
+
)
|
|
87
|
+
TRASH_ICON = _make_icon(
|
|
88
|
+
'<path stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M2 5h12M6 5V4a1 1 0 011-1h2a1 1 0 011 1v1"/><path stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M4.5 5l.7 7.5a1 1 0 001 .9h3.6a1 1 0 001-.9L11.5 5"/>',
|
|
89
|
+
"0 0 16 16"
|
|
90
|
+
)
|
|
91
|
+
USER_ICON = _make_icon(
|
|
92
|
+
'<circle cx="8" cy="6" r="2.5" stroke="currentColor" stroke-width="1.5"/><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"/>',
|
|
93
|
+
"0 0 16 16"
|
|
94
|
+
)
|
|
95
|
+
WARNING_ICON = _make_icon(
|
|
96
|
+
'<path stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M8 2L1.5 13.5h13L8 2z"/><path stroke="currentColor" stroke-width="1" stroke-linecap="round" d="M8 7v3"/><circle cx="8" cy="12" r="0.75" fill="currentColor"/>',
|
|
97
|
+
"0 0 16 16"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def check_circle_icon(props = nil) = CHECK_CIRCLE_ICON.call(props)
|
|
101
|
+
def chev_down_icon(props = nil) = CHEV_DOWN_ICON.call(props)
|
|
102
|
+
def chev_left_icon(props = nil) = CHEV_LEFT_ICON.call(props)
|
|
103
|
+
def chev_right_icon(props = nil) = CHEV_RIGHT_ICON.call(props)
|
|
104
|
+
def chev_up_icon(props = nil) = CHEV_UP_ICON.call(props)
|
|
105
|
+
def close_icon(props = nil) = CLOSE_ICON.call(props)
|
|
106
|
+
def copy_icon(props = nil) = COPY_ICON.call(props)
|
|
107
|
+
def danger_circle_icon(props = nil)= DANGER_CIRCLE_ICON.call(props)
|
|
108
|
+
def edit_icon(props = nil) = EDIT_ICON.call(props)
|
|
109
|
+
def external_link_icon(props = nil)= EXTERNAL_LINK_ICON.call(props)
|
|
110
|
+
def eye_icon(props = nil) = EYE_ICON.call(props)
|
|
111
|
+
def eye_off_icon(props = nil) = EYE_OFF_ICON.call(props)
|
|
112
|
+
def info_circle_icon(props = nil) = INFO_CIRCLE_ICON.call(props)
|
|
113
|
+
def menu_icon(props = nil) = MENU_ICON.call(props)
|
|
114
|
+
def plus_icon(props = nil) = PLUS_ICON.call(props)
|
|
115
|
+
def search_icon(props = nil) = SEARCH_ICON.call(props)
|
|
116
|
+
def trash_icon(props = nil) = TRASH_ICON.call(props)
|
|
117
|
+
def user_icon(props = nil) = USER_ICON.call(props)
|
|
118
|
+
def warning_icon(props = nil) = WARNING_ICON.call(props)
|
|
119
|
+
end
|
|
120
|
+
end
|
data/lib/klods/layout.rb
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
module Klods
|
|
2
|
+
module Layout
|
|
3
|
+
def page(a = nil, b = nil)
|
|
4
|
+
props, children = Core.normalize_args(a, b)
|
|
5
|
+
Core.build(
|
|
6
|
+
tag: "div", base: "klods-page",
|
|
7
|
+
modifiers: {
|
|
8
|
+
sidebar: "klods-page--with-sidebar",
|
|
9
|
+
sidebar_position: ->(v) { (v.to_s == "trailing") ? "klods-page--sidebar-trailing" : nil },
|
|
10
|
+
sticky_header: "klods-page--sticky-header"
|
|
11
|
+
},
|
|
12
|
+
props: props, children: children
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def header(a = nil, b = nil)
|
|
17
|
+
props, children = Core.normalize_args(a, b)
|
|
18
|
+
Core.build(tag: "header", base: "klods-header", props: props, children: children)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def sidebar(a = nil, b = nil)
|
|
22
|
+
props, children = Core.normalize_args(a, b)
|
|
23
|
+
Core.build(tag: "aside", base: "klods-sidebar", props: props, children: children)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def content(a = nil, b = nil)
|
|
27
|
+
props, children = Core.normalize_args(a, b)
|
|
28
|
+
Core.build(
|
|
29
|
+
tag: "main", base: "klods-content",
|
|
30
|
+
modifiers: {narrow: "klods-content--narrow"},
|
|
31
|
+
props: props, children: children
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def footer(a = nil, b = nil)
|
|
36
|
+
props, children = Core.normalize_args(a, b)
|
|
37
|
+
Core.build(tag: "footer", base: "klods-footer", props: props, children: children)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def section(a = nil, b = nil)
|
|
41
|
+
props, children = Core.normalize_args(a, b)
|
|
42
|
+
Core.build(tag: "section", base: "klods-section", props: props, children: children)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def stack(a = nil, b = nil)
|
|
46
|
+
props, children = Core.normalize_args(a, b)
|
|
47
|
+
Core.build(
|
|
48
|
+
tag: "div", base: "klods-stack",
|
|
49
|
+
modifiers: {gap: ->(v) { v ? "klods-stack--gap-#{v}" : nil }},
|
|
50
|
+
props: props, children: children
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def cluster(a = nil, b = nil)
|
|
55
|
+
props, children = Core.normalize_args(a, b)
|
|
56
|
+
Core.build(
|
|
57
|
+
tag: "div", base: "klods-cluster",
|
|
58
|
+
modifiers: {gap: ->(v) { v ? "klods-cluster--gap-#{v}" : nil }},
|
|
59
|
+
props: props, children: children
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def row(a = nil, b = nil)
|
|
64
|
+
props, children = Core.normalize_args(a, b)
|
|
65
|
+
Core.build(
|
|
66
|
+
tag: "div", base: "klods-row",
|
|
67
|
+
modifiers: {
|
|
68
|
+
gap: ->(v) { v ? "klods-row--gap-#{v}" : nil },
|
|
69
|
+
inline: "klods-row--inline"
|
|
70
|
+
},
|
|
71
|
+
props: props, children: children
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def grid(a = nil, b = nil)
|
|
76
|
+
props, children = Core.normalize_args(a, b)
|
|
77
|
+
Core.build(
|
|
78
|
+
tag: "div", base: "klods-grid",
|
|
79
|
+
modifiers: {
|
|
80
|
+
gap: ->(v) { v ? "klods-grid--gap-#{v}" : nil },
|
|
81
|
+
cols: ->(v) { v ? "klods-grid--cols-#{v}" : nil },
|
|
82
|
+
fit: "klods-grid--fit"
|
|
83
|
+
},
|
|
84
|
+
props: props, children: children
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def center(a = nil, b = nil)
|
|
89
|
+
props, children = Core.normalize_args(a, b)
|
|
90
|
+
Core.build(tag: "div", base: "klods-center", props: props, children: children)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def spread(a = nil, b = nil)
|
|
94
|
+
props, children = Core.normalize_args(a, b)
|
|
95
|
+
Core.build(tag: "div", base: "klods-spread", props: props, children: children)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def text(value)
|
|
99
|
+
Node.new("span", {}, [value.to_s])
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def sidebar_toggle(attrs = nil)
|
|
103
|
+
Core.el("button", {
|
|
104
|
+
"type" => "button",
|
|
105
|
+
"aria-label" => "Toggle sidebar",
|
|
106
|
+
"class" => "klods-sidebar-toggle"
|
|
107
|
+
}.merge((attrs || {}).transform_keys(&:to_s)))
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|