okonomi_ui_kit 0.1.6 → 0.1.7
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/app/assets/builds/okonomi_ui_kit/application.tailwind.css +33 -0
- data/app/helpers/okonomi_ui_kit/component.rb +62 -0
- data/app/helpers/okonomi_ui_kit/components/badge.rb +21 -16
- data/app/helpers/okonomi_ui_kit/components/button_to.rb +34 -0
- data/app/helpers/okonomi_ui_kit/components/code.rb +73 -0
- data/app/helpers/okonomi_ui_kit/components/link_to.rb +34 -0
- data/app/helpers/okonomi_ui_kit/components/page.rb +247 -0
- data/app/helpers/okonomi_ui_kit/components/table.rb +207 -0
- data/app/helpers/okonomi_ui_kit/components/typography.rb +29 -3
- data/app/helpers/okonomi_ui_kit/config.rb +16 -0
- data/app/helpers/okonomi_ui_kit/theme.rb +3 -3
- data/app/helpers/okonomi_ui_kit/ui_helper.rb +0 -40
- data/app/views/okonomi/components/code/_code.html.erb +1 -0
- data/app/views/okonomi/components/page/_page.html.erb +5 -0
- data/app/views/okonomi/components/table/_table.html.erb +3 -0
- data/lib/okonomi_ui_kit/engine.rb +0 -3
- data/lib/okonomi_ui_kit/version.rb +1 -1
- metadata +11 -5
- data/app/helpers/okonomi_ui_kit/badge_helper.rb +0 -23
- data/app/helpers/okonomi_ui_kit/page_builder_helper.rb +0 -217
- data/app/helpers/okonomi_ui_kit/table_helper.rb +0 -158
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c94c4c54d4385932abd25596610ea5e50f6d49477577a95731a0e0a1e22fca31
|
4
|
+
data.tar.gz: bb5f224ebe4b40a221cc382dea0256d75589557c553a3abf51f647cf3018044f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1668ef825c3f06032802b9614baba74626f6e74cabd741880143627d1f6c2ee32d1d2b0a21676a550d2773c3abaa1d4ec3162da05b7e4bcb574d5e8429bab8fc
|
7
|
+
data.tar.gz: 1f457860e311e5efa5fa07d8199c0ef0758dac7122b9a07b3780c820f472546aee2083bbaf2b0374343fc41882e7830780e788070111c04404f9f2788b7b25d3
|
@@ -619,6 +619,9 @@
|
|
619
619
|
.flex-shrink {
|
620
620
|
flex-shrink: 1;
|
621
621
|
}
|
622
|
+
.flex-shrink-0 {
|
623
|
+
flex-shrink: 0;
|
624
|
+
}
|
622
625
|
.shrink-0 {
|
623
626
|
flex-shrink: 0;
|
624
627
|
}
|
@@ -663,9 +666,15 @@
|
|
663
666
|
.transform {
|
664
667
|
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
665
668
|
}
|
669
|
+
.cursor-not-allowed {
|
670
|
+
cursor: not-allowed;
|
671
|
+
}
|
666
672
|
.cursor-pointer {
|
667
673
|
cursor: pointer;
|
668
674
|
}
|
675
|
+
.cursor-wait {
|
676
|
+
cursor: wait;
|
677
|
+
}
|
669
678
|
.resize {
|
670
679
|
resize: both;
|
671
680
|
}
|
@@ -1087,6 +1096,9 @@
|
|
1087
1096
|
.py-1 {
|
1088
1097
|
padding-block: calc(var(--spacing) * 1);
|
1089
1098
|
}
|
1099
|
+
.py-1\.5 {
|
1100
|
+
padding-block: calc(var(--spacing) * 1.5);
|
1101
|
+
}
|
1090
1102
|
.py-2 {
|
1091
1103
|
padding-block: calc(var(--spacing) * 2);
|
1092
1104
|
}
|
@@ -1159,6 +1171,9 @@
|
|
1159
1171
|
.align-middle {
|
1160
1172
|
vertical-align: middle;
|
1161
1173
|
}
|
1174
|
+
.font-mono {
|
1175
|
+
font-family: var(--font-mono);
|
1176
|
+
}
|
1162
1177
|
.text-2xl {
|
1163
1178
|
font-size: var(--text-2xl);
|
1164
1179
|
line-height: var(--tw-leading, var(--text-2xl--line-height));
|
@@ -1237,6 +1252,9 @@
|
|
1237
1252
|
.whitespace-nowrap {
|
1238
1253
|
white-space: nowrap;
|
1239
1254
|
}
|
1255
|
+
.text-amber-600 {
|
1256
|
+
color: var(--color-amber-600);
|
1257
|
+
}
|
1240
1258
|
.text-blue-500 {
|
1241
1259
|
color: var(--color-blue-500);
|
1242
1260
|
}
|
@@ -1357,6 +1375,9 @@
|
|
1357
1375
|
.opacity-0 {
|
1358
1376
|
opacity: 0%;
|
1359
1377
|
}
|
1378
|
+
.opacity-50 {
|
1379
|
+
opacity: 50%;
|
1380
|
+
}
|
1360
1381
|
.opacity-100 {
|
1361
1382
|
opacity: 100%;
|
1362
1383
|
}
|
@@ -1368,6 +1389,10 @@
|
|
1368
1389
|
--tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
1369
1390
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
1370
1391
|
}
|
1392
|
+
.shadow-none {
|
1393
|
+
--tw-shadow: 0 0 #0000;
|
1394
|
+
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
1395
|
+
}
|
1371
1396
|
.shadow-sm {
|
1372
1397
|
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
1373
1398
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
@@ -1380,6 +1405,10 @@
|
|
1380
1405
|
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
1381
1406
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
1382
1407
|
}
|
1408
|
+
.ring-2 {
|
1409
|
+
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
1410
|
+
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
1411
|
+
}
|
1383
1412
|
.ring-black {
|
1384
1413
|
--tw-ring-color: var(--color-black);
|
1385
1414
|
}
|
@@ -1395,6 +1424,10 @@
|
|
1395
1424
|
.ring-gray-300 {
|
1396
1425
|
--tw-ring-color: var(--color-gray-300);
|
1397
1426
|
}
|
1427
|
+
.ring-offset-2 {
|
1428
|
+
--tw-ring-offset-width: 2px;
|
1429
|
+
--tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
1430
|
+
}
|
1398
1431
|
.outline-hidden {
|
1399
1432
|
--tw-outline-style: none;
|
1400
1433
|
outline-style: none;
|
@@ -14,5 +14,67 @@ module OkonomiUiKit
|
|
14
14
|
def name
|
15
15
|
self.class.name.demodulize.underscore
|
16
16
|
end
|
17
|
+
|
18
|
+
def style(*args)
|
19
|
+
styles.dig(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def styles
|
23
|
+
@combined_styles ||= combined_styles
|
24
|
+
end
|
25
|
+
|
26
|
+
def combined_styles
|
27
|
+
internal_name = internal_styles_registry.has_key?(theme_name) ? theme_name : :default
|
28
|
+
config_name = config_styles_registry.has_key?(theme_name) ? theme_name : :default
|
29
|
+
|
30
|
+
internal_styles = internal_styles_registry[internal_name] || {}
|
31
|
+
config_styles = config_styles_registry[config_name] || {}
|
32
|
+
|
33
|
+
{}.deep_merge(internal_styles).deep_merge(config_styles)
|
34
|
+
end
|
35
|
+
|
36
|
+
def internal_styles_registry
|
37
|
+
self.class.internal_styles_registry
|
38
|
+
end
|
39
|
+
|
40
|
+
def config_styles_registry
|
41
|
+
self.class.config_styles_registry
|
42
|
+
end
|
43
|
+
|
44
|
+
def theme_name
|
45
|
+
:default
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.config_styles_registry
|
49
|
+
return Hash.new({}) unless config_class?
|
50
|
+
|
51
|
+
config_class.styles_registry
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.config_class
|
55
|
+
return nil unless config_class?
|
56
|
+
|
57
|
+
Object.const_get(config_class_name)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.config_class_name
|
61
|
+
"OkonomiUiKit::Configs::#{name.demodulize}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.config_class?
|
65
|
+
Object.const_defined?(config_class_name)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.register_styles(theme = :default, &block)
|
69
|
+
styles = block.call if block_given?
|
70
|
+
|
71
|
+
raise ArgumentError, "Styles must be a Hash" unless styles.is_a?(Hash)
|
72
|
+
|
73
|
+
internal_styles_registry[theme] = styles if styles.is_a?(Hash)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.internal_styles_registry
|
77
|
+
@internal_styles_registry ||= {}
|
78
|
+
end
|
17
79
|
end
|
18
80
|
end
|
@@ -2,24 +2,29 @@ module OkonomiUiKit
|
|
2
2
|
module Components
|
3
3
|
class Badge < OkonomiUiKit::Component
|
4
4
|
def render(text, options = {})
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
when :warning
|
14
|
-
"bg-yellow-100 text-yellow-800"
|
15
|
-
else
|
16
|
-
"bg-gray-100 text-gray-800"
|
17
|
-
end
|
5
|
+
options = options.with_indifferent_access
|
6
|
+
severity = (options.delete(:severity) || :default).to_sym
|
7
|
+
|
8
|
+
classes = [
|
9
|
+
style(:base),
|
10
|
+
style(:severities, severity) || '',
|
11
|
+
options.delete(:class) || ''
|
12
|
+
].reject(&:blank?).join(' ')
|
18
13
|
|
19
|
-
|
20
|
-
|
14
|
+
view.tag.span(text, class: classes, **options)
|
15
|
+
end
|
21
16
|
|
22
|
-
|
17
|
+
register_styles :default do
|
18
|
+
{
|
19
|
+
base: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium",
|
20
|
+
severities: {
|
21
|
+
default: "bg-gray-100 text-gray-800",
|
22
|
+
success: "bg-green-100 text-green-800",
|
23
|
+
danger: "bg-red-100 text-red-800",
|
24
|
+
info: "bg-blue-100 text-blue-800",
|
25
|
+
warning: "bg-yellow-100 text-yellow-800"
|
26
|
+
}
|
27
|
+
}
|
23
28
|
end
|
24
29
|
end
|
25
30
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module OkonomiUiKit
|
2
|
+
module Components
|
3
|
+
class ButtonTo < OkonomiUiKit::Component
|
4
|
+
def render(name = nil, options = nil, html_options = nil, &block)
|
5
|
+
html_options, options, name = options, name, block if block_given?
|
6
|
+
|
7
|
+
html_options ||= {}
|
8
|
+
html_options = html_options.with_indifferent_access
|
9
|
+
|
10
|
+
variant = (html_options.delete(:variant) || 'contained').to_sym
|
11
|
+
color = (html_options.delete(:color) || 'default').to_sym
|
12
|
+
|
13
|
+
html_options[:class] = build_button_class(variant: variant, color: color, classes: html_options[:class])
|
14
|
+
|
15
|
+
if block_given?
|
16
|
+
view.button_to(options, html_options, &block)
|
17
|
+
else
|
18
|
+
view.button_to(name, options, html_options)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def build_button_class(variant:, color:, classes: '')
|
25
|
+
[
|
26
|
+
theme.dig(:components, :link, :root) || '',
|
27
|
+
theme.dig(:components, :link, variant.to_sym, :root) || '',
|
28
|
+
theme.dig(:components, :link, variant.to_sym, :colors, color.to_sym) || '',
|
29
|
+
classes,
|
30
|
+
].reject(&:blank?).join(' ')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module OkonomiUiKit
|
2
|
+
module Components
|
3
|
+
class Code < OkonomiUiKit::Component
|
4
|
+
def render(content = nil, options = {}, &block)
|
5
|
+
options, content = content, nil if block_given?
|
6
|
+
options ||= {}
|
7
|
+
options = options.with_indifferent_access
|
8
|
+
|
9
|
+
# Extract component-specific options
|
10
|
+
language = options.delete(:language) || options.delete(:lang)
|
11
|
+
variant = (options.delete(:variant) || 'default').to_sym
|
12
|
+
size = (options.delete(:size) || 'default').to_sym
|
13
|
+
wrap = options.delete(:wrap) != false # Default to true
|
14
|
+
|
15
|
+
# Build classes
|
16
|
+
classes = build_classes(variant: variant, size: size, wrap: wrap, custom_class: options.delete(:class))
|
17
|
+
|
18
|
+
# Escape HTML entities in content
|
19
|
+
escaped_content = if block_given?
|
20
|
+
view.capture(&block)
|
21
|
+
elsif content
|
22
|
+
content
|
23
|
+
else
|
24
|
+
""
|
25
|
+
end
|
26
|
+
|
27
|
+
view.render(
|
28
|
+
template_path,
|
29
|
+
content: escaped_content.strip.html_safe,
|
30
|
+
options: options,
|
31
|
+
classes: classes,
|
32
|
+
language: language
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def build_classes(variant:, size:, wrap:, custom_class: nil)
|
39
|
+
base_classes = theme.dig(:components, :code, :base) || "bg-gray-900 text-gray-100 rounded-lg"
|
40
|
+
|
41
|
+
variant_classes = case variant
|
42
|
+
when :inline
|
43
|
+
"bg-gray-100 text-gray-900 px-1 py-0.5 rounded text-sm font-mono"
|
44
|
+
when :minimal
|
45
|
+
"bg-gray-900 text-gray-100 p-3 rounded text-xs"
|
46
|
+
else
|
47
|
+
# :default
|
48
|
+
"bg-gray-900 text-gray-100 p-4 rounded-lg"
|
49
|
+
end
|
50
|
+
|
51
|
+
size_classes = case size
|
52
|
+
when :xs
|
53
|
+
"text-xs"
|
54
|
+
when :sm
|
55
|
+
"text-sm"
|
56
|
+
when :lg
|
57
|
+
"text-base"
|
58
|
+
else
|
59
|
+
# :default
|
60
|
+
"text-sm"
|
61
|
+
end
|
62
|
+
|
63
|
+
wrap_classes = wrap ? "overflow-x-auto" : "overflow-hidden"
|
64
|
+
|
65
|
+
[base_classes, variant_classes, size_classes, wrap_classes, custom_class].compact.join(' ')
|
66
|
+
end
|
67
|
+
|
68
|
+
def html_escape(content)
|
69
|
+
ERB::Util.html_escape(content.to_s.strip)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module OkonomiUiKit
|
2
|
+
module Components
|
3
|
+
class LinkTo < OkonomiUiKit::Component
|
4
|
+
def render(name = nil, options = nil, html_options = nil, &block)
|
5
|
+
html_options, options, name = options, name, block if block_given?
|
6
|
+
|
7
|
+
html_options ||= {}
|
8
|
+
html_options = html_options.with_indifferent_access
|
9
|
+
|
10
|
+
variant = (html_options.delete(:variant) || 'text').to_sym
|
11
|
+
color = (html_options.delete(:color) || 'default').to_sym
|
12
|
+
|
13
|
+
html_options[:class] = build_button_class(variant: variant, color: color, classes: html_options[:class])
|
14
|
+
|
15
|
+
if block_given?
|
16
|
+
view.link_to(options, html_options, &block)
|
17
|
+
else
|
18
|
+
view.link_to(name, options, html_options)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def build_button_class(variant:, color:, classes: '')
|
25
|
+
[
|
26
|
+
theme.dig(:components, :link, :root) || '',
|
27
|
+
theme.dig(:components, :link, variant.to_sym, :root) || '',
|
28
|
+
theme.dig(:components, :link, variant.to_sym, :colors, color.to_sym) || '',
|
29
|
+
classes,
|
30
|
+
].reject(&:blank?).join(' ')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
module OkonomiUiKit
|
2
|
+
module Components
|
3
|
+
class Page < OkonomiUiKit::Component
|
4
|
+
def render(options = {}, &block)
|
5
|
+
builder = PageBuilder.new(view)
|
6
|
+
|
7
|
+
view.render(template_path, builder: builder, options: options, &block)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class PageBuilder
|
12
|
+
include ActionView::Helpers::TagHelper
|
13
|
+
include ActionView::Helpers::CaptureHelper
|
14
|
+
|
15
|
+
def initialize(template)
|
16
|
+
@template = template
|
17
|
+
@content_parts = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def page_header(**options, &block)
|
21
|
+
header_builder = PageHeaderBuilder.new(@template)
|
22
|
+
yield(header_builder) if block_given?
|
23
|
+
@content_parts << header_builder.render
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def section(**options, &block)
|
28
|
+
section_builder = SectionBuilder.new(@template)
|
29
|
+
section_builder.title(options[:title]) if options[:title]
|
30
|
+
yield(section_builder) if block_given?
|
31
|
+
@content_parts << section_builder.render
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def render_content
|
36
|
+
@template.safe_join(@content_parts)
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
render_content
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def tag
|
46
|
+
@template.tag
|
47
|
+
end
|
48
|
+
|
49
|
+
def capture(*args, &block)
|
50
|
+
@template.capture(*args, &block)
|
51
|
+
end
|
52
|
+
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.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
|
+
end
|
247
|
+
end
|