okonomi_ui_kit 0.1.8 → 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 +50 -6
- data/app/assets/builds/okonomi_ui_kit/application.tailwind.css +508 -225
- data/app/helpers/okonomi_ui_kit/CLAUDE.md +619 -0
- data/app/helpers/okonomi_ui_kit/application_helper.rb +8 -0
- data/app/helpers/okonomi_ui_kit/attribute_section_helper.rb +5 -5
- data/app/helpers/okonomi_ui_kit/component.rb +14 -6
- data/app/helpers/okonomi_ui_kit/components/alert.rb +1 -1
- data/app/helpers/okonomi_ui_kit/components/badge.rb +4 -4
- data/app/helpers/okonomi_ui_kit/components/breadcrumbs.rb +4 -4
- data/app/helpers/okonomi_ui_kit/components/button_base.rb +94 -22
- data/app/helpers/okonomi_ui_kit/components/button_tag.rb +14 -8
- data/app/helpers/okonomi_ui_kit/components/button_to.rb +8 -7
- data/app/helpers/okonomi_ui_kit/components/code.rb +41 -37
- data/app/helpers/okonomi_ui_kit/components/confirmation_modal.rb +130 -0
- data/app/helpers/okonomi_ui_kit/components/dropdown_button.rb +147 -0
- data/app/helpers/okonomi_ui_kit/components/forms/check_box_with_label.rb +38 -0
- data/app/helpers/okonomi_ui_kit/components/forms/collection_select.rb +57 -0
- data/app/helpers/okonomi_ui_kit/components/forms/date_field.rb +9 -0
- data/app/helpers/okonomi_ui_kit/components/forms/datetime_local_field.rb +9 -0
- data/app/helpers/okonomi_ui_kit/components/forms/email_field.rb +9 -0
- data/app/helpers/okonomi_ui_kit/components/forms/field.rb +24 -0
- data/app/helpers/okonomi_ui_kit/components/forms/field_set.rb +17 -0
- data/app/helpers/okonomi_ui_kit/components/forms/input_base.rb +57 -0
- data/app/helpers/okonomi_ui_kit/components/forms/label.rb +27 -0
- data/app/helpers/okonomi_ui_kit/components/forms/multi_select.rb +18 -0
- data/app/helpers/okonomi_ui_kit/components/forms/number_field.rb +9 -0
- data/app/helpers/okonomi_ui_kit/components/forms/password_field.rb +9 -0
- data/app/helpers/okonomi_ui_kit/components/forms/search_field.rb +9 -0
- data/app/helpers/okonomi_ui_kit/components/forms/select.rb +57 -0
- data/app/helpers/okonomi_ui_kit/components/forms/show_if.rb +28 -0
- data/app/helpers/okonomi_ui_kit/components/forms/telephone_field.rb +9 -0
- data/app/helpers/okonomi_ui_kit/components/forms/text_area.rb +9 -0
- data/app/helpers/okonomi_ui_kit/components/forms/text_field.rb +9 -0
- data/app/helpers/okonomi_ui_kit/components/forms/time_field.rb +9 -0
- data/app/helpers/okonomi_ui_kit/components/forms/upload_field.rb +25 -0
- data/app/helpers/okonomi_ui_kit/components/forms/url_field.rb +9 -0
- data/app/helpers/okonomi_ui_kit/components/forms.rb +6 -0
- data/app/helpers/okonomi_ui_kit/components/icon.rb +6 -6
- data/app/helpers/okonomi_ui_kit/components/link_to.rb +11 -10
- data/app/helpers/okonomi_ui_kit/components/navigation.rb +98 -0
- data/app/helpers/okonomi_ui_kit/components/page.rb +18 -203
- 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/helpers/okonomi_ui_kit/components/table.rb +7 -8
- data/app/helpers/okonomi_ui_kit/components/typography.rb +16 -16
- data/app/helpers/okonomi_ui_kit/components.rb +4 -0
- data/app/helpers/okonomi_ui_kit/configs.rb +4 -0
- data/app/helpers/okonomi_ui_kit/form_builder.rb +39 -130
- data/app/helpers/okonomi_ui_kit/form_component.rb +7 -0
- data/app/helpers/okonomi_ui_kit/svg_icons.rb +5 -5
- data/app/helpers/okonomi_ui_kit/t_w_merge.rb +33 -27
- data/app/helpers/okonomi_ui_kit/ui_helper.rb +17 -58
- data/app/javascript/okonomi_ui_kit/controllers/dropdown_controller.js +6 -0
- data/app/views/okonomi/components/confirmation_modal/_confirmation_modal.html.erb +76 -0
- data/app/views/okonomi/components/dropdown_button/_dropdown_button.html.erb +282 -0
- data/app/views/okonomi/components/forms/check_box_with_label/_check_box_with_label.html.erb +6 -0
- data/app/views/okonomi/{forms/tailwind → components/forms/field}/_field.html.erb +7 -7
- data/app/views/okonomi/components/forms/field_set/_field_set.html.erb +3 -0
- data/app/views/okonomi/components/forms/upload_field/_upload_field.html.erb +1 -0
- data/app/views/okonomi/components/navigation/_link.html.erb +18 -0
- data/app/views/okonomi/components/navigation/_navigation.html.erb +4 -0
- 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/app/views/okonomi/forms/tailwind/_checkbox_label.html.erb +2 -2
- data/app/views/okonomi/forms/tailwind/_multi_select.html.erb +2 -4
- data/app/views/okonomi/forms/tailwind/_upload_field.html.erb +10 -10
- data/config/importmap.rb +1 -1
- data/lib/okonomi_ui_kit/engine.rb +0 -1
- data/lib/okonomi_ui_kit/version.rb +1 -1
- metadata +47 -16
- data/app/helpers/okonomi_ui_kit/navigation_helper.rb +0 -72
- data/app/helpers/okonomi_ui_kit/theme.rb +0 -136
- data/app/helpers/okonomi_ui_kit/theme_helper.rb +0 -17
- data/app/views/okonomi/forms/tailwind/_field_set.html.erb +0 -3
- data/app/views/okonomi/modals/_confirmation_modal.html.erb +0 -77
- data/app/views/okonomi/navigation/_link.html.erb +0 -15
- data/app/views/okonomi/navigation/_menu.html.erb +0 -3
- data/app/views/okonomi/navigation/_navbar.html.erb +0 -105
- data/app/views/okonomi/page_builder/_page.html.erb +0 -3
@@ -2,7 +2,7 @@ module OkonomiUiKit
|
|
2
2
|
module Components
|
3
3
|
class Alert < OkonomiUiKit::Component
|
4
4
|
def render(title, options = {}, &block)
|
5
|
-
view.render(template_path, title
|
5
|
+
view.render(template_path, title: title, options: options.with_indifferent_access, &block)
|
6
6
|
end
|
7
7
|
end
|
8
8
|
end
|
@@ -5,11 +5,11 @@ module OkonomiUiKit
|
|
5
5
|
options = options.with_indifferent_access
|
6
6
|
severity = (options.delete(:severity) || options.delete(:variant) || :default).to_sym
|
7
7
|
|
8
|
-
classes =
|
8
|
+
classes = tw_merge(
|
9
9
|
style(:base),
|
10
|
-
style(:severities, severity)
|
11
|
-
options.delete(:class)
|
12
|
-
|
10
|
+
style(:severities, severity),
|
11
|
+
options.delete(:class)
|
12
|
+
)
|
13
13
|
|
14
14
|
view.tag.span(text, class: classes, **options)
|
15
15
|
end
|
@@ -28,7 +28,7 @@ module OkonomiUiKit
|
|
28
28
|
}
|
29
29
|
end
|
30
30
|
|
31
|
-
def initialize(template
|
31
|
+
def initialize(template)
|
32
32
|
super
|
33
33
|
@items = []
|
34
34
|
@builder = BreadcrumbBuilder.new(self)
|
@@ -36,9 +36,9 @@ module OkonomiUiKit
|
|
36
36
|
|
37
37
|
def render(options = {}, &block)
|
38
38
|
return "" if block.nil?
|
39
|
-
|
39
|
+
|
40
40
|
block.call(@builder)
|
41
|
-
view.render("okonomi/components/breadcrumbs/breadcrumbs",
|
41
|
+
view.render("okonomi/components/breadcrumbs/breadcrumbs",
|
42
42
|
component: self,
|
43
43
|
items: @items,
|
44
44
|
options: options
|
@@ -66,4 +66,4 @@ module OkonomiUiKit
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
end
|
69
|
-
end
|
69
|
+
end
|
@@ -1,40 +1,108 @@
|
|
1
1
|
module OkonomiUiKit
|
2
2
|
module Components
|
3
3
|
class ButtonBase < OkonomiUiKit::Component
|
4
|
-
def build_button_class(variant:, color:, classes:
|
4
|
+
def build_button_class(variant:, color:, classes: "")
|
5
5
|
[
|
6
|
-
style(:root) ||
|
7
|
-
style(variant.to_sym, :root) ||
|
8
|
-
style(variant.to_sym, :colors, color.to_sym) ||
|
9
|
-
classes
|
10
|
-
].reject(&:blank?).join(
|
6
|
+
style(:root) || "",
|
7
|
+
style(variant.to_sym, :root) || "",
|
8
|
+
style(variant.to_sym, :colors, color.to_sym) || "",
|
9
|
+
classes
|
10
|
+
].reject(&:blank?).join(" ")
|
11
|
+
end
|
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
|
11
79
|
end
|
12
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,22 +2,28 @@ 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
|
-
variant = (options.delete(:variant) ||
|
11
|
-
color = (options.delete(:color) ||
|
15
|
+
variant = (options.delete(:variant) || "contained").to_sym
|
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
|
22
28
|
end
|
23
|
-
end
|
29
|
+
end
|
@@ -7,17 +7,18 @@ module OkonomiUiKit
|
|
7
7
|
html_options ||= {}
|
8
8
|
html_options = html_options.with_indifferent_access
|
9
9
|
|
10
|
-
variant = (html_options.delete(:variant) ||
|
11
|
-
color = (html_options.delete(:color) ||
|
10
|
+
variant = (html_options.delete(:variant) || "contained").to_sym
|
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
|
22
23
|
end
|
23
|
-
end
|
24
|
+
end
|
@@ -8,61 +8,65 @@ module OkonomiUiKit
|
|
8
8
|
|
9
9
|
# Extract component-specific options
|
10
10
|
language = options.delete(:language) || options.delete(:lang)
|
11
|
-
variant = (options.delete(:variant) ||
|
12
|
-
size = (options.delete(:size) ||
|
11
|
+
variant = (options.delete(:variant) || "default").to_sym
|
12
|
+
size = (options.delete(:size) || "default").to_sym
|
13
13
|
wrap = options.delete(:wrap) != false # Default to true
|
14
14
|
|
15
15
|
# Build classes
|
16
16
|
classes = build_classes(variant: variant, size: size, wrap: wrap, custom_class: options.delete(:class))
|
17
17
|
|
18
18
|
# Escape HTML entities in content
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
19
|
+
raw_content = if block_given?
|
20
|
+
view.capture(&block)
|
21
|
+
elsif content
|
22
|
+
content
|
23
|
+
else
|
24
|
+
""
|
25
|
+
end
|
26
|
+
|
27
|
+
escaped_content = html_escape(raw_content)
|
26
28
|
|
27
29
|
view.render(
|
28
30
|
template_path,
|
29
|
-
content: escaped_content
|
31
|
+
content: escaped_content,
|
30
32
|
options: options,
|
31
33
|
classes: classes,
|
32
34
|
language: language
|
33
35
|
)
|
34
36
|
end
|
35
37
|
|
38
|
+
register_styles :default do
|
39
|
+
{
|
40
|
+
base: "",
|
41
|
+
variants: {
|
42
|
+
default: "bg-gray-900 text-gray-100 p-4 rounded-lg",
|
43
|
+
inline: "bg-gray-100 text-gray-900 px-1 py-0.5 rounded text-sm font-mono",
|
44
|
+
minimal: "bg-gray-900 text-gray-100 p-3 rounded text-xs"
|
45
|
+
},
|
46
|
+
sizes: {
|
47
|
+
xs: "text-xs",
|
48
|
+
sm: "text-sm",
|
49
|
+
default: "text-sm",
|
50
|
+
lg: "text-base"
|
51
|
+
},
|
52
|
+
wrap: {
|
53
|
+
true: "overflow-x-auto",
|
54
|
+
false: "overflow-hidden"
|
55
|
+
}
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
36
59
|
private
|
37
60
|
|
38
61
|
def build_classes(variant:, size:, wrap:, custom_class: nil)
|
39
|
-
base_classes =
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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"
|
62
|
+
base_classes = style(:base) || ""
|
63
|
+
variant_classes = style(:variants, variant) || ""
|
64
|
+
size_classes = style(:sizes, size) || ""
|
65
|
+
# Convert boolean wrap to symbol for hash access
|
66
|
+
wrap_key = wrap ? :true : :false
|
67
|
+
wrap_classes = style(:wrap, wrap_key) || ""
|
64
68
|
|
65
|
-
[base_classes, variant_classes, size_classes, wrap_classes, custom_class].
|
69
|
+
[ base_classes, variant_classes, size_classes, wrap_classes, custom_class ].reject(&:blank?).join(" ")
|
66
70
|
end
|
67
71
|
|
68
72
|
def html_escape(content)
|
@@ -70,4 +74,4 @@ module OkonomiUiKit
|
|
70
74
|
end
|
71
75
|
end
|
72
76
|
end
|
73
|
-
end
|
77
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module OkonomiUiKit
|
2
|
+
module Components
|
3
|
+
class ConfirmationModal < OkonomiUiKit::Component
|
4
|
+
def render(options = {}, &block)
|
5
|
+
options = options.with_indifferent_access
|
6
|
+
|
7
|
+
# Extract and validate required options
|
8
|
+
title = options.fetch(:title) { raise ArgumentError, "title is required" }
|
9
|
+
message = options.fetch(:message) { raise ArgumentError, "message is required" }
|
10
|
+
|
11
|
+
# Extract optional parameters with defaults
|
12
|
+
confirm_text = options.delete(:confirm_text) || "Confirm"
|
13
|
+
cancel_text = options.delete(:cancel_text) || "Cancel"
|
14
|
+
variant = (options.delete(:variant) || :warning).to_sym
|
15
|
+
size = (options.delete(:size) || :md).to_sym
|
16
|
+
auto_open = options.delete(:auto_open) || false
|
17
|
+
|
18
|
+
# Build component options
|
19
|
+
modal_options = {
|
20
|
+
title: title,
|
21
|
+
message: message,
|
22
|
+
confirm_text: confirm_text,
|
23
|
+
cancel_text: cancel_text,
|
24
|
+
variant: variant,
|
25
|
+
size: size,
|
26
|
+
auto_open: auto_open,
|
27
|
+
has_custom_actions: block_given?,
|
28
|
+
data: options.delete(:data) || {}
|
29
|
+
}.merge(options)
|
30
|
+
|
31
|
+
view.render(template_path, component: self, options: modal_options, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Register default styles for the confirmation modal
|
35
|
+
register_styles :default do
|
36
|
+
{
|
37
|
+
# Modal container and backdrop
|
38
|
+
backdrop: "fixed inset-0 bg-gray-500/75 transition-opacity duration-300 ease-out opacity-0",
|
39
|
+
container: "fixed inset-0 z-10 w-screen overflow-y-auto",
|
40
|
+
wrapper: "flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0",
|
41
|
+
|
42
|
+
# Modal panel
|
43
|
+
panel: {
|
44
|
+
base: "relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all duration-300 ease-out sm:my-8 sm:w-full sm:p-6 opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
|
45
|
+
sizes: {
|
46
|
+
sm: "sm:max-w-sm",
|
47
|
+
md: "sm:max-w-lg",
|
48
|
+
lg: "sm:max-w-2xl",
|
49
|
+
xl: "sm:max-w-4xl"
|
50
|
+
}
|
51
|
+
},
|
52
|
+
|
53
|
+
# Close button
|
54
|
+
close_button: {
|
55
|
+
wrapper: "absolute top-0 right-0 hidden pt-4 pr-4 sm:block",
|
56
|
+
button: "rounded-md bg-white text-gray-400 hover:text-gray-500 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none",
|
57
|
+
icon: {
|
58
|
+
file: "heroicons/outline/x-mark",
|
59
|
+
class: "size-6"
|
60
|
+
}
|
61
|
+
},
|
62
|
+
|
63
|
+
# Icon configuration
|
64
|
+
icon: {
|
65
|
+
wrapper: "mx-auto flex size-12 shrink-0 items-center justify-center rounded-full sm:mx-0 sm:size-10",
|
66
|
+
class: "size-6",
|
67
|
+
variants: {
|
68
|
+
warning: {
|
69
|
+
wrapper: "bg-red-100",
|
70
|
+
icon: "text-red-600",
|
71
|
+
file: "heroicons/outline/exclamation-triangle"
|
72
|
+
},
|
73
|
+
info: {
|
74
|
+
wrapper: "bg-blue-100",
|
75
|
+
icon: "text-blue-600",
|
76
|
+
file: "heroicons/outline/information-circle"
|
77
|
+
},
|
78
|
+
success: {
|
79
|
+
wrapper: "bg-green-100",
|
80
|
+
icon: "text-green-600",
|
81
|
+
file: "heroicons/outline/check-circle"
|
82
|
+
}
|
83
|
+
}
|
84
|
+
},
|
85
|
+
|
86
|
+
# Content styling
|
87
|
+
content: {
|
88
|
+
wrapper: "sm:flex sm:items-start",
|
89
|
+
text_wrapper: "mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left",
|
90
|
+
title: "text-base font-semibold text-gray-900",
|
91
|
+
message: "mt-2 text-sm text-gray-500"
|
92
|
+
},
|
93
|
+
|
94
|
+
# Actions container
|
95
|
+
actions: {
|
96
|
+
wrapper: "mt-5 sm:mt-4 sm:flex sm:flex-row-reverse"
|
97
|
+
}
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
# Helper methods to build classes from styles
|
102
|
+
def modal_panel_class(size)
|
103
|
+
[
|
104
|
+
style(:panel, :base),
|
105
|
+
style(:panel, :sizes, size)
|
106
|
+
].compact.join(" ")
|
107
|
+
end
|
108
|
+
|
109
|
+
def modal_icon_wrapper_class(variant)
|
110
|
+
[
|
111
|
+
style(:icon, :wrapper),
|
112
|
+
style(:icon, :variants, variant, :wrapper)
|
113
|
+
].compact.join(" ")
|
114
|
+
end
|
115
|
+
|
116
|
+
def modal_icon_class(variant)
|
117
|
+
[
|
118
|
+
style(:icon, :class),
|
119
|
+
style(:icon, :variants, variant, :icon)
|
120
|
+
].compact.join(" ")
|
121
|
+
end
|
122
|
+
|
123
|
+
def modal_data_attributes(options)
|
124
|
+
return "" unless options[:data]
|
125
|
+
|
126
|
+
options[:data].map { |k, v| "data-#{k.to_s.dasherize}=\"#{v}\"" }.join(" ").html_safe
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
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
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module OkonomiUiKit
|
2
|
+
module Components
|
3
|
+
module Forms
|
4
|
+
class CheckBoxWithLabel < OkonomiUiKit::FormComponent
|
5
|
+
def render(form, method, options = {}, checked_value = true, unchecked_value = false)
|
6
|
+
options = options.with_indifferent_access
|
7
|
+
|
8
|
+
view.content_tag(:div, class: style(:wrapper)) do
|
9
|
+
view.concat form.check_box(
|
10
|
+
method,
|
11
|
+
{
|
12
|
+
class: style(:input, :root)
|
13
|
+
}.merge(options || {}),
|
14
|
+
checked_value,
|
15
|
+
unchecked_value
|
16
|
+
)
|
17
|
+
view.concat view.render(template_path, component: self, method: method, options: options, form: form)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
register_styles :default do
|
22
|
+
{
|
23
|
+
wrapper: "flex gap-x-3",
|
24
|
+
input: {
|
25
|
+
root: "size-4 rounded border-gray-300 text-primary-600 focus:ring-primary-600"
|
26
|
+
},
|
27
|
+
label: {
|
28
|
+
root: "text-sm/6 font-medium text-gray-900"
|
29
|
+
},
|
30
|
+
hint: {
|
31
|
+
root: "text-gray-500"
|
32
|
+
}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|