okonomi_ui_kit 0.1.9 → 0.1.10
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/README.md +46 -4
- data/app/assets/builds/okonomi_ui_kit/application.tailwind.css +140 -79
- data/app/helpers/okonomi_ui_kit/CLAUDE.md +619 -0
- data/app/helpers/okonomi_ui_kit/component.rb +4 -0
- data/app/helpers/okonomi_ui_kit/components/badge.rb +4 -4
- data/app/helpers/okonomi_ui_kit/components/button_base.rb +88 -16
- data/app/helpers/okonomi_ui_kit/components/button_tag.rb +11 -5
- data/app/helpers/okonomi_ui_kit/components/button_to.rb +5 -4
- data/app/helpers/okonomi_ui_kit/components/dropdown_button.rb +147 -0
- data/app/helpers/okonomi_ui_kit/components/link_to.rb +5 -4
- data/app/helpers/okonomi_ui_kit/components/page.rb +16 -201
- data/app/helpers/okonomi_ui_kit/components/page_header.rb +111 -0
- data/app/helpers/okonomi_ui_kit/components/page_section.rb +145 -0
- data/app/javascript/okonomi_ui_kit/controllers/dropdown_controller.js +6 -0
- data/app/views/okonomi/components/dropdown_button/_dropdown_button.html.erb +282 -0
- data/app/views/okonomi/components/forms/field/_field.html.erb +34 -3
- data/app/views/okonomi/components/page/_page.html.erb +1 -1
- data/app/views/okonomi/components/page_header/_page_header.html.erb +4 -0
- data/app/views/okonomi/components/page_section/_page_section.html.erb +4 -0
- data/lib/okonomi_ui_kit/version.rb +1 -1
- metadata +13 -8
- data/app/views/okonomi/forms/tailwind/_field.html.erb +0 -34
- data/app/views/okonomi/page_builder/_page.html.erb +0 -3
@@ -10,31 +10,99 @@ module OkonomiUiKit
|
|
10
10
|
].reject(&:blank?).join(" ")
|
11
11
|
end
|
12
12
|
|
13
|
+
# Extracts and normalizes icon configuration from options
|
14
|
+
# Returns [icon_config, updated_options]
|
15
|
+
# icon_config will be nil or { path: "icon/path", position: :start/:end }
|
16
|
+
def extract_icon_config(options)
|
17
|
+
return [nil, options] unless options.is_a?(Hash)
|
18
|
+
|
19
|
+
icon_option = options.delete(:icon)
|
20
|
+
return [nil, options] unless icon_option
|
21
|
+
|
22
|
+
icon_config = case icon_option
|
23
|
+
when String
|
24
|
+
{ path: icon_option, position: :start }
|
25
|
+
when Hash
|
26
|
+
if icon_option[:start]
|
27
|
+
{ path: icon_option[:start], position: :start }
|
28
|
+
elsif icon_option[:end]
|
29
|
+
{ path: icon_option[:end], position: :end }
|
30
|
+
else
|
31
|
+
# Invalid hash format, ignore
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
else
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
[icon_config, options]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Renders button content with optional icon
|
42
|
+
# icon_config: { path: "icon/path", position: :start/:end }
|
43
|
+
# content: String or block content
|
44
|
+
# block: Optional block for content
|
45
|
+
def render_button_content(icon_config, content = nil, &block)
|
46
|
+
icon_html = if icon_config
|
47
|
+
view.ui.icon(icon_config[:path], class: style(:icon, icon_config[:position]))
|
48
|
+
end
|
49
|
+
|
50
|
+
content_html = if block_given?
|
51
|
+
view.capture(&block)
|
52
|
+
else
|
53
|
+
content
|
54
|
+
end
|
55
|
+
|
56
|
+
# Check if we have actual content (not empty/nil)
|
57
|
+
has_content = content_html.present?
|
58
|
+
|
59
|
+
if icon_config && has_content
|
60
|
+
# Both icon and content - wrap in flex container with gap
|
61
|
+
wrapper_class = "inline-flex items-center gap-1.5"
|
62
|
+
|
63
|
+
if icon_config[:position] == :end
|
64
|
+
view.content_tag(:span, class: wrapper_class) do
|
65
|
+
view.safe_join([content_html, icon_html].compact)
|
66
|
+
end
|
67
|
+
else
|
68
|
+
view.content_tag(:span, class: wrapper_class) do
|
69
|
+
view.safe_join([icon_html, content_html].compact)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
elsif icon_config
|
73
|
+
# Icon only - no wrapper needed
|
74
|
+
icon_html
|
75
|
+
else
|
76
|
+
# Content only
|
77
|
+
content_html
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
13
81
|
register_styles :default do
|
14
82
|
{
|
15
83
|
root: "hover:cursor-pointer text-sm",
|
16
84
|
outlined: {
|
17
|
-
root: "inline-flex border items-center justify-center px-2 py-1 rounded-md font-medium focus:outline-none
|
85
|
+
root: "inline-flex border items-center justify-center px-2 py-1 rounded-md font-medium focus:outline-none",
|
18
86
|
colors: {
|
19
|
-
default: "bg-white text-default-700 border-default-700 hover:bg-default-50",
|
20
|
-
primary: "bg-white text-primary-600 border-primary-600 hover:bg-primary-50",
|
21
|
-
secondary: "bg-white text-secondary-600 border-secondary-600 hover:bg-secondary-50",
|
22
|
-
success: "bg-white text-success-600 border-success-600 hover:bg-success-50",
|
23
|
-
danger: "bg-white text-danger-600 border-danger-600 hover:bg-danger-50",
|
24
|
-
warning: "bg-white text-warning-600 border-warning-600 hover:bg-warning-50",
|
25
|
-
info: "bg-white text-info-600 border-info-600 hover:bg-info-50"
|
87
|
+
default: "bg-white text-default-700 border-default-700 hover:bg-default-50 active:bg-default-100",
|
88
|
+
primary: "bg-white text-primary-600 border-primary-600 hover:bg-primary-50 active:bg-primary-100",
|
89
|
+
secondary: "bg-white text-secondary-600 border-secondary-600 hover:bg-secondary-50 active:bg-secondary-100",
|
90
|
+
success: "bg-white text-success-600 border-success-600 hover:bg-success-50 active:bg-success-100",
|
91
|
+
danger: "bg-white text-danger-600 border-danger-600 hover:bg-danger-50 active:bg-danger-100",
|
92
|
+
warning: "bg-white text-warning-600 border-warning-600 hover:bg-warning-50 active:bg-warning-100",
|
93
|
+
info: "bg-white text-info-600 border-info-600 hover:bg-info-50 active:bg-info-100"
|
26
94
|
}
|
27
95
|
},
|
28
96
|
contained: {
|
29
|
-
root: "inline-flex border items-center justify-center px-2 py-1 rounded-md font-medium focus:outline-none
|
97
|
+
root: "inline-flex border items-center justify-center px-2 py-1 rounded-md font-medium focus:outline-none",
|
30
98
|
colors: {
|
31
|
-
default: "border-default-700 bg-default-600 text-white hover:bg-default-700",
|
32
|
-
primary: "border-primary-700 bg-primary-600 text-white hover:bg-primary-700",
|
33
|
-
secondary: "border-secondary-700 bg-secondary-600 text-white hover:bg-secondary-700",
|
34
|
-
success: "border-success-700 bg-success-600 text-white hover:bg-success-700",
|
35
|
-
danger: "border-danger-700 bg-danger-600 text-white hover:bg-danger-700",
|
36
|
-
warning: "border-warning-700 bg-warning-600 text-white hover:bg-warning-700",
|
37
|
-
info: "border-info-700 bg-info-600 text-white hover:bg-info-700"
|
99
|
+
default: "border-default-700 bg-default-600 text-white hover:bg-default-700 active:bg-default-500",
|
100
|
+
primary: "border-primary-700 bg-primary-600 text-white hover:bg-primary-700 active:bg-primary-500",
|
101
|
+
secondary: "border-secondary-700 bg-secondary-600 text-white hover:bg-secondary-700 active:bg-secondary-500",
|
102
|
+
success: "border-success-700 bg-success-600 text-white hover:bg-success-700 active:bg-success-500",
|
103
|
+
danger: "border-danger-700 bg-danger-600 text-white hover:bg-danger-700 active:bg-danger-500",
|
104
|
+
warning: "border-warning-700 bg-warning-600 text-white hover:bg-warning-700 active:bg-warning-500",
|
105
|
+
info: "border-info-700 bg-info-600 text-white hover:bg-info-700 active:bg-info-500"
|
38
106
|
}
|
39
107
|
},
|
40
108
|
text: {
|
@@ -48,6 +116,10 @@ module OkonomiUiKit
|
|
48
116
|
warning: "text-warning-600 hover:underline",
|
49
117
|
info: "text-info-600 hover:underline"
|
50
118
|
}
|
119
|
+
},
|
120
|
+
icon: {
|
121
|
+
start: "size-3.5",
|
122
|
+
end: "size-3.5"
|
51
123
|
}
|
52
124
|
}
|
53
125
|
end
|
@@ -2,20 +2,26 @@ module OkonomiUiKit
|
|
2
2
|
module Components
|
3
3
|
class ButtonTag < OkonomiUiKit::Components::ButtonBase
|
4
4
|
def render(name = nil, options = {}, &block)
|
5
|
-
|
5
|
+
# Handle different parameter patterns
|
6
|
+
if name.is_a?(Hash) && options.empty?
|
7
|
+
# Called as button_tag(options) with block
|
8
|
+
options = name
|
9
|
+
name = nil
|
10
|
+
end
|
6
11
|
|
7
12
|
options ||= {}
|
8
13
|
options = options.with_indifferent_access
|
9
14
|
|
10
15
|
variant = (options.delete(:variant) || "contained").to_sym
|
11
16
|
color = (options.delete(:color) || "default").to_sym
|
17
|
+
|
18
|
+
# Extract icon configuration
|
19
|
+
icon_config, options = extract_icon_config(options)
|
12
20
|
|
13
21
|
options[:class] = build_button_class(variant: variant, color: color, classes: options[:class])
|
14
22
|
|
15
|
-
|
16
|
-
|
17
|
-
else
|
18
|
-
view.button_tag(name, options)
|
23
|
+
view.button_tag(options) do
|
24
|
+
render_button_content(icon_config, name, &block)
|
19
25
|
end
|
20
26
|
end
|
21
27
|
end
|
@@ -9,13 +9,14 @@ module OkonomiUiKit
|
|
9
9
|
|
10
10
|
variant = (html_options.delete(:variant) || "contained").to_sym
|
11
11
|
color = (html_options.delete(:color) || "default").to_sym
|
12
|
+
|
13
|
+
# Extract icon configuration
|
14
|
+
icon_config, html_options = extract_icon_config(html_options)
|
12
15
|
|
13
16
|
html_options[:class] = build_button_class(variant: variant, color: color, classes: html_options[:class])
|
14
17
|
|
15
|
-
|
16
|
-
|
17
|
-
else
|
18
|
-
view.button_to(name, options, html_options)
|
18
|
+
view.button_to(options, html_options) do
|
19
|
+
render_button_content(icon_config, name, &block)
|
19
20
|
end
|
20
21
|
end
|
21
22
|
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module OkonomiUiKit
|
2
|
+
module Components
|
3
|
+
class DropdownButton < ButtonBase
|
4
|
+
def render(options = {}, &block)
|
5
|
+
raise ArgumentError, "DropdownButton requires a block" unless block_given?
|
6
|
+
|
7
|
+
options = options.with_indifferent_access
|
8
|
+
variant = (options.delete(:variant) || "contained").to_sym
|
9
|
+
color = (options.delete(:color) || "default").to_sym
|
10
|
+
|
11
|
+
base_button_classes = build_button_class(
|
12
|
+
variant: variant,
|
13
|
+
color: color,
|
14
|
+
classes: options.delete(:class)
|
15
|
+
)
|
16
|
+
|
17
|
+
menu_classes = [
|
18
|
+
style(:menu, :root),
|
19
|
+
options.delete(:menu_class)
|
20
|
+
].compact.join(" ")
|
21
|
+
|
22
|
+
dropdown_builder = DropdownBuilder.new(view)
|
23
|
+
|
24
|
+
view.render(
|
25
|
+
template_path,
|
26
|
+
base_button_classes: base_button_classes,
|
27
|
+
menu_classes: menu_classes,
|
28
|
+
dropdown_builder: dropdown_builder,
|
29
|
+
component: self,
|
30
|
+
options: options,
|
31
|
+
&block
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
register_styles :default do
|
36
|
+
{
|
37
|
+
primary: {
|
38
|
+
icon: "mr-1.5 size-3.5",
|
39
|
+
chevron: "size-3.5"
|
40
|
+
},
|
41
|
+
menu: {
|
42
|
+
root: "absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-gray-200 focus:outline-none",
|
43
|
+
divider: "h-0 my-1 border-t border-gray-200",
|
44
|
+
item: {
|
45
|
+
root: "hover:cursor-pointer block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 active:bg-gray-100 hover:text-gray-900",
|
46
|
+
icon: "mr-3 h-5 w-5 text-gray-400",
|
47
|
+
label: "flex items-center"
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
class DropdownBuilder
|
54
|
+
attr_reader :view, :items, :is_first
|
55
|
+
|
56
|
+
def initialize(view)
|
57
|
+
@view = view
|
58
|
+
@items = []
|
59
|
+
@is_first = true
|
60
|
+
end
|
61
|
+
|
62
|
+
def link_to(name = nil, options = nil, html_options = nil, &block)
|
63
|
+
# Handle icon extraction
|
64
|
+
if html_options.is_a?(Hash)
|
65
|
+
icon = html_options.delete(:icon)
|
66
|
+
else
|
67
|
+
icon = nil
|
68
|
+
end
|
69
|
+
|
70
|
+
item = {
|
71
|
+
type: :link,
|
72
|
+
name: name,
|
73
|
+
options: options,
|
74
|
+
html_options: html_options || {},
|
75
|
+
block: block,
|
76
|
+
is_first: @is_first,
|
77
|
+
icon: icon
|
78
|
+
}
|
79
|
+
@items << item
|
80
|
+
@is_first = false
|
81
|
+
end
|
82
|
+
|
83
|
+
def button_to(name = nil, options = {}, html_options = {}, &block)
|
84
|
+
# Handle icon extraction
|
85
|
+
if html_options.is_a?(Hash)
|
86
|
+
icon = html_options.delete(:icon)
|
87
|
+
else
|
88
|
+
icon = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
item = {
|
92
|
+
type: :button,
|
93
|
+
name: name,
|
94
|
+
options: options,
|
95
|
+
html_options: html_options,
|
96
|
+
block: block,
|
97
|
+
is_first: @is_first,
|
98
|
+
icon: icon
|
99
|
+
}
|
100
|
+
@items << item
|
101
|
+
@is_first = false
|
102
|
+
end
|
103
|
+
|
104
|
+
def button_tag(content_or_options = nil, options = nil, &block)
|
105
|
+
# Handle the different argument patterns for button_tag
|
106
|
+
if content_or_options.is_a?(Hash)
|
107
|
+
options = content_or_options
|
108
|
+
content = nil
|
109
|
+
else
|
110
|
+
content = content_or_options
|
111
|
+
end
|
112
|
+
|
113
|
+
options ||= {}
|
114
|
+
|
115
|
+
# Handle icon extraction
|
116
|
+
icon = options.delete(:icon) if options.is_a?(Hash)
|
117
|
+
|
118
|
+
# Ensure type is button for button_tag
|
119
|
+
options[:type] ||= "button"
|
120
|
+
|
121
|
+
item = {
|
122
|
+
type: :button_tag,
|
123
|
+
name: content,
|
124
|
+
options: options,
|
125
|
+
block: block,
|
126
|
+
is_first: @is_first,
|
127
|
+
icon: icon
|
128
|
+
}
|
129
|
+
@items << item
|
130
|
+
@is_first = false
|
131
|
+
end
|
132
|
+
|
133
|
+
def divider
|
134
|
+
@items << { type: :divider }
|
135
|
+
end
|
136
|
+
|
137
|
+
def primary_item
|
138
|
+
@items.find { |item| item[:is_first] && item[:type] != :divider }
|
139
|
+
end
|
140
|
+
|
141
|
+
def menu_items
|
142
|
+
@items
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -9,13 +9,14 @@ module OkonomiUiKit
|
|
9
9
|
|
10
10
|
variant = (html_options.delete(:variant) || "text").to_sym
|
11
11
|
color = (html_options.delete(:color) || "default").to_sym
|
12
|
+
|
13
|
+
# Extract icon configuration
|
14
|
+
icon_config, html_options = extract_icon_config(html_options)
|
12
15
|
|
13
16
|
html_options[:class] = build_button_class(variant: variant, color: color, classes: html_options[:class])
|
14
17
|
|
15
|
-
|
16
|
-
|
17
|
-
else
|
18
|
-
view.link_to(name, options, html_options)
|
18
|
+
view.link_to(options, html_options) do
|
19
|
+
render_button_content(icon_config, name, &block)
|
19
20
|
end
|
20
21
|
end
|
21
22
|
end
|
@@ -2,9 +2,22 @@ module OkonomiUiKit
|
|
2
2
|
module Components
|
3
3
|
class Page < OkonomiUiKit::Component
|
4
4
|
def render(options = {}, &block)
|
5
|
+
options = options.with_indifferent_access
|
6
|
+
|
7
|
+
classes = tw_merge(
|
8
|
+
style(:root),
|
9
|
+
options.delete(:class)
|
10
|
+
)
|
11
|
+
|
5
12
|
builder = PageBuilder.new(view)
|
6
13
|
|
7
|
-
view.render(template_path, builder: builder, options: options, &block)
|
14
|
+
view.render(template_path, builder: builder, options: options.merge(class: classes), &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
register_styles :default do
|
18
|
+
{
|
19
|
+
root: "flex flex-col gap-8 p-8"
|
20
|
+
}
|
8
21
|
end
|
9
22
|
end
|
10
23
|
|
@@ -18,17 +31,12 @@ module OkonomiUiKit
|
|
18
31
|
end
|
19
32
|
|
20
33
|
def page_header(**options, &block)
|
21
|
-
|
22
|
-
yield(header_builder) if block_given?
|
23
|
-
@content_parts << header_builder.render
|
34
|
+
@content_parts << @template.ui.page_header(options, &block)
|
24
35
|
nil
|
25
36
|
end
|
26
37
|
|
27
38
|
def section(**options, &block)
|
28
|
-
|
29
|
-
section_builder.title(options[:title]) if options[:title]
|
30
|
-
yield(section_builder) if block_given?
|
31
|
-
@content_parts << section_builder.render
|
39
|
+
@content_parts << @template.ui.page_section(options, &block)
|
32
40
|
nil
|
33
41
|
end
|
34
42
|
|
@@ -50,198 +58,5 @@ module OkonomiUiKit
|
|
50
58
|
@template.capture(*args, &block)
|
51
59
|
end
|
52
60
|
end
|
53
|
-
|
54
|
-
class PageHeaderBuilder
|
55
|
-
include ActionView::Helpers::TagHelper
|
56
|
-
include ActionView::Helpers::CaptureHelper
|
57
|
-
|
58
|
-
def initialize(template)
|
59
|
-
@template = template
|
60
|
-
@breadcrumbs_content = nil
|
61
|
-
@row_content = nil
|
62
|
-
end
|
63
|
-
|
64
|
-
def breadcrumbs(&block)
|
65
|
-
@breadcrumbs_content = @template.ui.breadcrumbs(&block)
|
66
|
-
end
|
67
|
-
|
68
|
-
def row(&block)
|
69
|
-
row_builder = PageHeaderRowBuilder.new(@template)
|
70
|
-
yield(row_builder) if block_given?
|
71
|
-
@row_content = row_builder.render
|
72
|
-
end
|
73
|
-
|
74
|
-
def render
|
75
|
-
content = []
|
76
|
-
content << @breadcrumbs_content if @breadcrumbs_content
|
77
|
-
content << @row_content if @row_content
|
78
|
-
|
79
|
-
tag.div(class: "flex flex-col gap-2") do
|
80
|
-
@template.safe_join(content.compact)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
private
|
85
|
-
|
86
|
-
def tag
|
87
|
-
@template.tag
|
88
|
-
end
|
89
|
-
|
90
|
-
def capture(*args, &block)
|
91
|
-
@template.capture(*args, &block)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
class PageHeaderRowBuilder
|
96
|
-
include ActionView::Helpers::TagHelper
|
97
|
-
include ActionView::Helpers::CaptureHelper
|
98
|
-
|
99
|
-
def initialize(template)
|
100
|
-
@template = template
|
101
|
-
@title_content = nil
|
102
|
-
@actions_content = nil
|
103
|
-
end
|
104
|
-
|
105
|
-
def title(text, **options)
|
106
|
-
@title_content = tag.h1(text, class: "text-2xl font-bold leading-7 text-gray-900 truncate sm:text-3xl sm:tracking-tight")
|
107
|
-
end
|
108
|
-
|
109
|
-
def actions(&block)
|
110
|
-
@actions_content = tag.div(class: "mt-4 flex md:ml-4 md:mt-0 gap-2") do
|
111
|
-
capture(&block) if block_given?
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def render
|
116
|
-
tag.div(class: "flex w-full justify-between items-center") do
|
117
|
-
content = []
|
118
|
-
content << @title_content if @title_content
|
119
|
-
content << @actions_content if @actions_content
|
120
|
-
@template.safe_join(content.compact)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
private
|
125
|
-
|
126
|
-
def tag
|
127
|
-
@template.tag
|
128
|
-
end
|
129
|
-
|
130
|
-
def capture(*args, &block)
|
131
|
-
@template.capture(*args, &block)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
class SectionBuilder
|
136
|
-
include ActionView::Helpers::TagHelper
|
137
|
-
include ActionView::Helpers::CaptureHelper
|
138
|
-
|
139
|
-
def initialize(template)
|
140
|
-
@template = template
|
141
|
-
@title_content = nil
|
142
|
-
@subtitle_content = nil
|
143
|
-
@actions_content = nil
|
144
|
-
@body_content = nil
|
145
|
-
@attributes = []
|
146
|
-
end
|
147
|
-
|
148
|
-
def title(text, **options)
|
149
|
-
@title_content = tag.h3(text, class: "text-base/7 font-semibold text-gray-900")
|
150
|
-
end
|
151
|
-
|
152
|
-
def subtitle(text, **options)
|
153
|
-
@subtitle_content = tag.p(text, class: "mt-1 max-w-2xl text-sm/6 text-gray-500")
|
154
|
-
end
|
155
|
-
|
156
|
-
def actions(&block)
|
157
|
-
@actions_content = tag.div(class: "mt-4 flex md:ml-4 md:mt-0") do
|
158
|
-
capture(&block) if block_given?
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def body(&block)
|
163
|
-
if block_given?
|
164
|
-
# Capture the content first to see if attributes were used
|
165
|
-
content = capture { yield(self) }
|
166
|
-
|
167
|
-
@body_content = if @attributes.any?
|
168
|
-
# If attributes were added, wrap them in dl
|
169
|
-
tag.div do
|
170
|
-
tag.dl(class: "divide-y divide-gray-100") do
|
171
|
-
@template.safe_join(@attributes)
|
172
|
-
end
|
173
|
-
end
|
174
|
-
else
|
175
|
-
# Otherwise, just return the captured content
|
176
|
-
tag.div do
|
177
|
-
content
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
def attribute(label, value = nil, **options, &block)
|
184
|
-
content = if block_given?
|
185
|
-
capture(&block)
|
186
|
-
elsif value.respond_to?(:call)
|
187
|
-
value.call
|
188
|
-
else
|
189
|
-
value
|
190
|
-
end
|
191
|
-
|
192
|
-
attribute_html = tag.div(class: "py-6 sm:grid sm:grid-cols-3 sm:gap-4") do
|
193
|
-
dt_content = tag.dt(label, class: "text-sm font-medium text-gray-900")
|
194
|
-
dd_content = tag.dd(content, class: "mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0")
|
195
|
-
|
196
|
-
dt_content + dd_content
|
197
|
-
end
|
198
|
-
|
199
|
-
@attributes << attribute_html
|
200
|
-
end
|
201
|
-
|
202
|
-
def render
|
203
|
-
tag.div(class: "overflow-hidden bg-white") do
|
204
|
-
header_content = build_header
|
205
|
-
content_parts = []
|
206
|
-
content_parts << header_content if header_content.present?
|
207
|
-
content_parts << @body_content if @body_content
|
208
|
-
@template.safe_join(content_parts.compact)
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
private
|
213
|
-
|
214
|
-
def build_header
|
215
|
-
return nil unless @title_content || @subtitle_content || @actions_content
|
216
|
-
|
217
|
-
tag.div(class: "py-6") do
|
218
|
-
if @actions_content
|
219
|
-
tag.div(class: "flex w-full justify-between items-start") do
|
220
|
-
title_section = tag.div do
|
221
|
-
content_parts = []
|
222
|
-
content_parts << @title_content if @title_content
|
223
|
-
content_parts << @subtitle_content if @subtitle_content
|
224
|
-
@template.safe_join(content_parts.compact)
|
225
|
-
end
|
226
|
-
|
227
|
-
title_section + @actions_content
|
228
|
-
end
|
229
|
-
else
|
230
|
-
content_parts = []
|
231
|
-
content_parts << @title_content if @title_content
|
232
|
-
content_parts << @subtitle_content if @subtitle_content
|
233
|
-
@template.safe_join(content_parts.compact)
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
def tag
|
239
|
-
@template.tag
|
240
|
-
end
|
241
|
-
|
242
|
-
def capture(*args, &block)
|
243
|
-
@template.capture(*args, &block)
|
244
|
-
end
|
245
|
-
end
|
246
61
|
end
|
247
62
|
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module OkonomiUiKit
|
2
|
+
module Components
|
3
|
+
class PageHeader < OkonomiUiKit::Component
|
4
|
+
def render(options = {}, &block)
|
5
|
+
options = options.with_indifferent_access
|
6
|
+
|
7
|
+
classes = tw_merge(
|
8
|
+
style(:root),
|
9
|
+
options.delete(:class)
|
10
|
+
)
|
11
|
+
|
12
|
+
builder = PageHeaderBuilder.new(view, self)
|
13
|
+
|
14
|
+
view.render(template_path, builder: builder, options: options.merge(class: classes), &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
register_styles :default do
|
18
|
+
{
|
19
|
+
root: "flex flex-col gap-2",
|
20
|
+
row: "flex w-full justify-between items-center",
|
21
|
+
actions: "mt-4 flex md:ml-4 md:mt-0 gap-2"
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class PageHeaderBuilder
|
27
|
+
include ActionView::Helpers::TagHelper
|
28
|
+
include ActionView::Helpers::CaptureHelper
|
29
|
+
|
30
|
+
def initialize(template, component)
|
31
|
+
@template = template
|
32
|
+
@component = component
|
33
|
+
@breadcrumbs_content = nil
|
34
|
+
@row_content = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def breadcrumbs(&block)
|
38
|
+
@breadcrumbs_content = @template.ui.breadcrumbs(&block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def row(&block)
|
42
|
+
row_builder = PageHeaderRowBuilder.new(@template, @component)
|
43
|
+
yield(row_builder) if block_given?
|
44
|
+
@row_content = row_builder.render
|
45
|
+
end
|
46
|
+
|
47
|
+
def render
|
48
|
+
content = []
|
49
|
+
content << @breadcrumbs_content if @breadcrumbs_content
|
50
|
+
content << @row_content if @row_content
|
51
|
+
|
52
|
+
@template.safe_join(content.compact)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def tag
|
58
|
+
@template.tag
|
59
|
+
end
|
60
|
+
|
61
|
+
def capture(*args, &block)
|
62
|
+
@template.capture(*args, &block)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class PageHeaderRowBuilder
|
67
|
+
include ActionView::Helpers::TagHelper
|
68
|
+
include ActionView::Helpers::CaptureHelper
|
69
|
+
|
70
|
+
attr_reader :template
|
71
|
+
|
72
|
+
delegate :ui, to: :template
|
73
|
+
|
74
|
+
def initialize(template, component)
|
75
|
+
@template = template
|
76
|
+
@component = component
|
77
|
+
@title_content = nil
|
78
|
+
@actions_content = nil
|
79
|
+
end
|
80
|
+
|
81
|
+
def title(text, **options)
|
82
|
+
@title_content = ui.typography(text, variant: "h1", **options)
|
83
|
+
end
|
84
|
+
|
85
|
+
def actions(&block)
|
86
|
+
@actions_content = tag.div(class: @component.style(:actions)) do
|
87
|
+
capture(&block) if block_given?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def render
|
92
|
+
tag.div(class: @component.style(:row)) do
|
93
|
+
content = []
|
94
|
+
content << @title_content if @title_content
|
95
|
+
content << @actions_content if @actions_content
|
96
|
+
@template.safe_join(content.compact)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def tag
|
103
|
+
@template.tag
|
104
|
+
end
|
105
|
+
|
106
|
+
def capture(*args, &block)
|
107
|
+
@template.capture(*args, &block)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|