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.
@@ -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
@@ -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
@@ -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