okonomi_ui_kit 0.1.9 → 0.1.11
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 +366 -57
- 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/helpers/okonomi_ui_kit/t_w_merge.rb +34 -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
@@ -0,0 +1,145 @@
|
|
1
|
+
module OkonomiUiKit
|
2
|
+
module Components
|
3
|
+
class PageSection < OkonomiUiKit::Component
|
4
|
+
def render(options = {}, &block)
|
5
|
+
options = options.with_indifferent_access
|
6
|
+
title = options.delete(:title)
|
7
|
+
|
8
|
+
classes = tw_merge(
|
9
|
+
style(:root),
|
10
|
+
options.delete(:class)
|
11
|
+
)
|
12
|
+
|
13
|
+
builder = SectionBuilder.new(view, self)
|
14
|
+
builder.title(title) if title
|
15
|
+
|
16
|
+
view.render(template_path, builder: builder, options: options.merge(class: classes), &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
register_styles :default do
|
20
|
+
{
|
21
|
+
root: "overflow-hidden bg-white",
|
22
|
+
header: "py-6",
|
23
|
+
header_with_actions: "flex w-full justify-between items-start",
|
24
|
+
title: "text-base/7 font-semibold text-gray-900",
|
25
|
+
subtitle: "mt-1 max-w-2xl text-sm/6 text-gray-500",
|
26
|
+
actions: "mt-4 flex md:ml-4 md:mt-0",
|
27
|
+
attribute_list: "divide-y divide-gray-100",
|
28
|
+
attribute_row: "py-6 sm:grid sm:grid-cols-3 sm:gap-4",
|
29
|
+
attribute_label: "text-sm font-medium text-gray-900",
|
30
|
+
attribute_value: "mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0"
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class SectionBuilder
|
36
|
+
include ActionView::Helpers::TagHelper
|
37
|
+
include ActionView::Helpers::CaptureHelper
|
38
|
+
|
39
|
+
def initialize(template, component)
|
40
|
+
@template = template
|
41
|
+
@component = component
|
42
|
+
@title_content = nil
|
43
|
+
@subtitle_content = nil
|
44
|
+
@actions_content = nil
|
45
|
+
@body_content = nil
|
46
|
+
@attributes = []
|
47
|
+
end
|
48
|
+
|
49
|
+
def title(text, **options)
|
50
|
+
@title_content = tag.h3(text, class: @component.style(:title))
|
51
|
+
end
|
52
|
+
|
53
|
+
def subtitle(text, **options)
|
54
|
+
@subtitle_content = tag.p(text, class: @component.style(:subtitle))
|
55
|
+
end
|
56
|
+
|
57
|
+
def actions(&block)
|
58
|
+
@actions_content = tag.div(class: @component.style(:actions)) do
|
59
|
+
capture(&block) if block_given?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def body(&block)
|
64
|
+
if block_given?
|
65
|
+
# Capture the content first to see if attributes were used
|
66
|
+
content = capture { yield(self) }
|
67
|
+
|
68
|
+
@body_content = if @attributes.any?
|
69
|
+
# If attributes were added, wrap them in dl
|
70
|
+
tag.div do
|
71
|
+
tag.dl(class: @component.style(:attribute_list)) do
|
72
|
+
@template.safe_join(@attributes)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
else
|
76
|
+
# Otherwise, just return the captured content
|
77
|
+
tag.div do
|
78
|
+
content
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def attribute(label, value = nil, **options, &block)
|
85
|
+
content = if block_given?
|
86
|
+
capture(&block)
|
87
|
+
elsif value.respond_to?(:call)
|
88
|
+
value.call
|
89
|
+
else
|
90
|
+
value
|
91
|
+
end
|
92
|
+
|
93
|
+
attribute_html = tag.div(class: @component.style(:attribute_row)) do
|
94
|
+
dt_content = tag.dt(label, class: @component.style(:attribute_label))
|
95
|
+
dd_content = tag.dd(content, class: @component.style(:attribute_value))
|
96
|
+
|
97
|
+
dt_content + dd_content
|
98
|
+
end
|
99
|
+
|
100
|
+
@attributes << attribute_html
|
101
|
+
end
|
102
|
+
|
103
|
+
def render_header
|
104
|
+
return nil unless @title_content || @subtitle_content || @actions_content
|
105
|
+
|
106
|
+
tag.div(class: @component.style(:header)) do
|
107
|
+
if @actions_content
|
108
|
+
tag.div(class: @component.style(:header_with_actions)) do
|
109
|
+
title_section = tag.div do
|
110
|
+
content_parts = []
|
111
|
+
content_parts << @title_content if @title_content
|
112
|
+
content_parts << @subtitle_content if @subtitle_content
|
113
|
+
@template.safe_join(content_parts.compact)
|
114
|
+
end
|
115
|
+
|
116
|
+
title_section + @actions_content
|
117
|
+
end
|
118
|
+
else
|
119
|
+
content_parts = []
|
120
|
+
content_parts << @title_content if @title_content
|
121
|
+
content_parts << @subtitle_content if @subtitle_content
|
122
|
+
@template.safe_join(content_parts.compact)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def render_content
|
128
|
+
content_parts = []
|
129
|
+
content_parts << render_header
|
130
|
+
content_parts << @body_content if @body_content
|
131
|
+
@template.safe_join(content_parts.compact)
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def tag
|
137
|
+
@template.tag
|
138
|
+
end
|
139
|
+
|
140
|
+
def capture(*args, &block)
|
141
|
+
@template.capture(*args, &block)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -76,6 +76,40 @@ module OkonomiUiKit
|
|
76
76
|
[ /^items-(?:start|end|center|baseline|stretch)$/, :align_items ],
|
77
77
|
[ /^justify-(?:start|end|center|between|around|evenly)$/, :justify_content ],
|
78
78
|
|
79
|
+
# Spacing - Padding
|
80
|
+
[ /^p(?:-(?:\d+|px|\[\S+\]))?$/, :padding_all ],
|
81
|
+
[ /^px(?:-(?:\d+|px|\[\S+\]))?$/, :padding_x ],
|
82
|
+
[ /^py(?:-(?:\d+|px|\[\S+\]))?$/, :padding_y ],
|
83
|
+
[ /^pl(?:-(?:\d+|px|\[\S+\]))?$/, :padding_left ],
|
84
|
+
[ /^pr(?:-(?:\d+|px|\[\S+\]))?$/, :padding_right ],
|
85
|
+
[ /^pt(?:-(?:\d+|px|\[\S+\]))?$/, :padding_top ],
|
86
|
+
[ /^pb(?:-(?:\d+|px|\[\S+\]))?$/, :padding_bottom ],
|
87
|
+
|
88
|
+
# Spacing - Margin (including negative margins)
|
89
|
+
[ /^-?m(?:-(?:\d+|px|auto|\[\S+\]))?$/, :margin_all ],
|
90
|
+
[ /^-?mx(?:-(?:\d+|px|auto|\[\S+\]))?$/, :margin_x ],
|
91
|
+
[ /^-?my(?:-(?:\d+|px|auto|\[\S+\]))?$/, :margin_y ],
|
92
|
+
[ /^-?ml(?:-(?:\d+|px|auto|\[\S+\]))?$/, :margin_left ],
|
93
|
+
[ /^-?mr(?:-(?:\d+|px|auto|\[\S+\]))?$/, :margin_right ],
|
94
|
+
[ /^-?mt(?:-(?:\d+|px|auto|\[\S+\]))?$/, :margin_top ],
|
95
|
+
[ /^-?mb(?:-(?:\d+|px|auto|\[\S+\]))?$/, :margin_bottom ],
|
96
|
+
|
97
|
+
# Gap
|
98
|
+
[ /^gap(?:-(?:\d+|px|\[\S+\]))?$/, :gap_all ],
|
99
|
+
[ /^gap-x(?:-(?:\d+|px|\[\S+\]))?$/, :gap_x ],
|
100
|
+
[ /^gap-y(?:-(?:\d+|px|\[\S+\]))?$/, :gap_y ],
|
101
|
+
|
102
|
+
# Space between children
|
103
|
+
[ /^-?space-x(?:-(?:\d+|px|reverse|\[\S+\]))?$/, :space_x ],
|
104
|
+
[ /^-?space-y(?:-(?:\d+|px|reverse|\[\S+\]))?$/, :space_y ],
|
105
|
+
|
106
|
+
# Ring
|
107
|
+
[ /^ring(?:-(?:\d+|inset|\[\S+\]))?$/, :ring_width ],
|
108
|
+
[ /^ring-opacity-(?:\d{1,3}|\[.+\])$/, :ring_opacity ],
|
109
|
+
[ /^ring-(?:inherit|current|transparent|black|white|[a-z]+-(?:\d{2,3}|950)|\[[^\]]+\])$/, :ring_color ],
|
110
|
+
[ /^ring-offset(?:-(?:\d+|\[\S+\]))?$/, :ring_offset_width ],
|
111
|
+
[ /^ring-offset-(?:inherit|current|transparent|black|white|[a-z]+-(?:\d{2,3}|950)|\[[^\]]+\])$/, :ring_offset_color ],
|
112
|
+
|
79
113
|
# Borders
|
80
114
|
[ /^(?:border|border-(?:\d+|\[\S+\]))$/, :border_width_overall ],
|
81
115
|
[ /^border-[trblxy](?:-\d+|\[\S+\])?$/, :border_width_side ],
|
@@ -19,6 +19,12 @@ export default class extends Controller {
|
|
19
19
|
this.menuTarget.classList.add("hidden")
|
20
20
|
}
|
21
21
|
|
22
|
+
closeDeferred() {
|
23
|
+
setTimeout(() => {
|
24
|
+
this.close()
|
25
|
+
}, 50)
|
26
|
+
}
|
27
|
+
|
22
28
|
closeOnClickOutside(event) {
|
23
29
|
if (!this.element.contains(event.target)) {
|
24
30
|
this.close()
|
@@ -0,0 +1,282 @@
|
|
1
|
+
<%
|
2
|
+
# Extract dropdown builder from block execution
|
3
|
+
yield(dropdown_builder)
|
4
|
+
|
5
|
+
primary = dropdown_builder.primary_item
|
6
|
+
menu_items = dropdown_builder.menu_items
|
7
|
+
|
8
|
+
# Check if there are any menu items beyond the primary
|
9
|
+
has_menu_items = menu_items.any? { |item| item != primary }
|
10
|
+
|
11
|
+
# For split buttons, we need to handle borders specially for outlined variant
|
12
|
+
is_outlined = base_button_classes.include?("border")
|
13
|
+
is_text_variant = !base_button_classes.include?("border") && !base_button_classes.include?("bg-")
|
14
|
+
|
15
|
+
# Build classes for the primary action
|
16
|
+
if is_text_variant
|
17
|
+
# Text variant: keep normal rounded corners
|
18
|
+
primary_classes = base_button_classes
|
19
|
+
.gsub(/px-2/, "px-2 pr-3") # Increase right padding
|
20
|
+
else
|
21
|
+
# Other variants: remove right border radius and handle borders
|
22
|
+
primary_classes = base_button_classes
|
23
|
+
.gsub(/rounded-md/, "rounded-l-md")
|
24
|
+
.gsub(/px-2/, "px-2 pr-3") # Increase right padding
|
25
|
+
|
26
|
+
# Add border-r-0 for outlined variant to remove double border
|
27
|
+
primary_classes += " border-r-0" if is_outlined
|
28
|
+
end
|
29
|
+
|
30
|
+
# Build classes for dropdown trigger
|
31
|
+
if is_text_variant
|
32
|
+
# Text variant: keep normal rounded corners and add spacing
|
33
|
+
trigger_classes = base_button_classes
|
34
|
+
.gsub(/px-\d+/, "px-2") + " ml-1"
|
35
|
+
else
|
36
|
+
# Other variants: remove left border radius and overlap
|
37
|
+
trigger_classes = base_button_classes
|
38
|
+
.gsub(/rounded-md/, "rounded-r-md")
|
39
|
+
.gsub(/px-\d+/, "px-2") + " -ml-px"
|
40
|
+
end
|
41
|
+
%>
|
42
|
+
|
43
|
+
<div <%= tag.attributes(options.merge(
|
44
|
+
class: ["relative inline-block", options[:class]].compact.join(" "),
|
45
|
+
data: (options[:data] || {}).merge(controller: "dropdown", action: "click@window->dropdown#closeOnClickOutside")
|
46
|
+
)) %>>
|
47
|
+
<% if primary && !has_menu_items %>
|
48
|
+
<%# Render only primary button when no menu items %>
|
49
|
+
<% if primary[:type] == :link %>
|
50
|
+
<%= link_to primary[:options], primary[:html_options].merge(
|
51
|
+
class: base_button_classes
|
52
|
+
) do %>
|
53
|
+
<% if primary[:icon] %>
|
54
|
+
<%= ui.icon(primary[:icon], class: component.style(:primary, :icon)) %>
|
55
|
+
<% end %>
|
56
|
+
<% if primary[:block] %>
|
57
|
+
<%= capture(&primary[:block]) %>
|
58
|
+
<% else %>
|
59
|
+
<%= primary[:name] %>
|
60
|
+
<% end %>
|
61
|
+
<% end %>
|
62
|
+
<% elsif primary[:type] == :button %>
|
63
|
+
<%= button_to primary[:options], primary[:html_options].merge(
|
64
|
+
class: base_button_classes
|
65
|
+
) do %>
|
66
|
+
<% if primary[:icon] %>
|
67
|
+
<%= ui.icon(primary[:icon], class: component.style(:primary, :icon)) %>
|
68
|
+
<% end %>
|
69
|
+
<% if primary[:block] %>
|
70
|
+
<%= capture(&primary[:block]) %>
|
71
|
+
<% else %>
|
72
|
+
<%= primary[:name] %>
|
73
|
+
<% end %>
|
74
|
+
<% end %>
|
75
|
+
<% elsif primary[:type] == :button_tag %>
|
76
|
+
<%= button_tag primary[:options].merge(
|
77
|
+
class: base_button_classes
|
78
|
+
) do %>
|
79
|
+
<% if primary[:icon] %>
|
80
|
+
<%= ui.icon(primary[:icon], class: component.style(:primary, :icon)) %>
|
81
|
+
<% end %>
|
82
|
+
<% if primary[:block] %>
|
83
|
+
<%= capture(&primary[:block]) %>
|
84
|
+
<% else %>
|
85
|
+
<%= primary[:name] %>
|
86
|
+
<% end %>
|
87
|
+
<% end %>
|
88
|
+
<% end %>
|
89
|
+
<% elsif primary && has_menu_items %>
|
90
|
+
<div class="relative inline-flex">
|
91
|
+
<% if primary[:type] == :link %>
|
92
|
+
<% primary_options = primary[:html_options].dup %>
|
93
|
+
<%= link_to primary[:options], primary_options.merge(
|
94
|
+
class: primary_classes
|
95
|
+
) do %>
|
96
|
+
<% if primary[:icon] %>
|
97
|
+
<%= ui.icon(primary[:icon], class: component.style(:primary, :icon)) %>
|
98
|
+
<% end %>
|
99
|
+
<% if primary[:block] %>
|
100
|
+
<%= capture(&primary[:block]) %>
|
101
|
+
<% else %>
|
102
|
+
<%= primary[:name] %>
|
103
|
+
<% end %>
|
104
|
+
<% end %>
|
105
|
+
<% elsif primary[:type] == :button %>
|
106
|
+
<% primary_options = primary[:html_options].dup %>
|
107
|
+
<%= button_to primary[:options], primary_options.merge(
|
108
|
+
class: primary_classes
|
109
|
+
) do %>
|
110
|
+
<% if primary[:icon] %>
|
111
|
+
<%= ui.icon(primary[:icon], class: component.style(:primary, :icon)) %>
|
112
|
+
<% end %>
|
113
|
+
<% if primary[:block] %>
|
114
|
+
<%= capture(&primary[:block]) %>
|
115
|
+
<% else %>
|
116
|
+
<%= primary[:name] %>
|
117
|
+
<% end %>
|
118
|
+
<% end %>
|
119
|
+
<% elsif primary[:type] == :button_tag %>
|
120
|
+
<% primary_options = primary[:options].dup %>
|
121
|
+
<%= button_tag primary_options.merge(
|
122
|
+
class: primary_classes
|
123
|
+
) do %>
|
124
|
+
<% if primary[:icon] %>
|
125
|
+
<%= ui.icon(primary[:icon], class: component.style(:primary, :icon)) %>
|
126
|
+
<% end %>
|
127
|
+
<% if primary[:block] %>
|
128
|
+
<%= capture(&primary[:block]) %>
|
129
|
+
<% else %>
|
130
|
+
<%= primary[:name] %>
|
131
|
+
<% end %>
|
132
|
+
<% end %>
|
133
|
+
<% end %>
|
134
|
+
|
135
|
+
<button type="button"
|
136
|
+
class="<%= trigger_classes %>"
|
137
|
+
data-action="click->dropdown#toggle"
|
138
|
+
aria-haspopup="true"
|
139
|
+
aria-expanded="false">
|
140
|
+
<span class="sr-only">Open options</span>
|
141
|
+
<%= ui.icon("heroicons/solid/chevron-down", class: component.style(:primary, :chevron)) %>
|
142
|
+
</button>
|
143
|
+
</div>
|
144
|
+
<% else %>
|
145
|
+
<button type="button"
|
146
|
+
class="<%= base_button_classes %>"
|
147
|
+
data-action="click->dropdown#toggle"
|
148
|
+
aria-haspopup="true"
|
149
|
+
aria-expanded="false">
|
150
|
+
Options
|
151
|
+
<%= ui.icon("heroicons/solid/chevron-down", class: "-mr-1 h-5 w-5") %>
|
152
|
+
</button>
|
153
|
+
<% end %>
|
154
|
+
|
155
|
+
<% if has_menu_items %>
|
156
|
+
<div class="<%= menu_classes %> hidden" data-dropdown-target="menu">
|
157
|
+
<div class="py-1" role="menu" aria-orientation="vertical">
|
158
|
+
<% # Check if any menu item has an icon to ensure consistent alignment %>
|
159
|
+
<% has_any_icon = menu_items.any? { |item| item != primary && item[:icon] && item[:type] != :divider } %>
|
160
|
+
|
161
|
+
<% menu_items.each do |item| %>
|
162
|
+
<% next if item == primary %> <%# Skip the primary item in the menu %>
|
163
|
+
|
164
|
+
<% if item[:type] == :divider %>
|
165
|
+
<div class="<%= component.style(:menu, :divider) %>" role="separator"></div>
|
166
|
+
<% elsif item[:type] == :link %>
|
167
|
+
<% link_options = item[:html_options].dup %>
|
168
|
+
<% icon_path = item[:icon] %>
|
169
|
+
<% link_class = [component.style(:menu, :item, :root), link_options.delete(:class)].compact.join(" ") %>
|
170
|
+
|
171
|
+
<% # Add deferred close action %>
|
172
|
+
<% link_options[:data] ||= {} %>
|
173
|
+
<% existing_action = link_options[:data][:action] || link_options.delete("data-action") %>
|
174
|
+
<% link_options[:data][:action] = [existing_action, "click->dropdown#closeDeferred"].compact.join(" ") %>
|
175
|
+
|
176
|
+
<%= link_to item[:options], link_options.merge(
|
177
|
+
class: link_class,
|
178
|
+
role: "menuitem"
|
179
|
+
) do %>
|
180
|
+
<% if has_any_icon %>
|
181
|
+
<span class="<%= component.style(:menu, :item, :label) %>">
|
182
|
+
<% if icon_path %>
|
183
|
+
<%= ui.icon(icon_path, class: component.style(:menu, :item, :icon)) %>
|
184
|
+
<% else %>
|
185
|
+
<span class="<%= component.style(:menu, :item, :icon) %>"></span>
|
186
|
+
<% end %>
|
187
|
+
<% if item[:block] %>
|
188
|
+
<%= capture(&item[:block]) %>
|
189
|
+
<% else %>
|
190
|
+
<%= item[:name] %>
|
191
|
+
<% end %>
|
192
|
+
</span>
|
193
|
+
<% else %>
|
194
|
+
<% if item[:block] %>
|
195
|
+
<%= capture(&item[:block]) %>
|
196
|
+
<% else %>
|
197
|
+
<%= item[:name] %>
|
198
|
+
<% end %>
|
199
|
+
<% end %>
|
200
|
+
<% end %>
|
201
|
+
<% elsif item[:type] == :button %>
|
202
|
+
<% button_options = item[:html_options].dup %>
|
203
|
+
<% icon_path = item[:icon] %>
|
204
|
+
<% button_class = [component.style(:menu, :item, :root), button_options.delete(:class)].compact.join(" ") %>
|
205
|
+
|
206
|
+
<% # Add deferred close action %>
|
207
|
+
<% button_options[:data] ||= {} %>
|
208
|
+
<% existing_action = button_options[:data][:action] || button_options.delete("data-action") %>
|
209
|
+
<% button_options[:data][:action] = [existing_action, "click->dropdown#closeDeferred"].compact.join(" ") %>
|
210
|
+
|
211
|
+
<%= button_to item[:options], button_options.merge(
|
212
|
+
class: button_class,
|
213
|
+
role: "menuitem"
|
214
|
+
) do %>
|
215
|
+
<% if has_any_icon %>
|
216
|
+
<span class="<%= component.style(:menu, :item, :label) %>">
|
217
|
+
<% if icon_path %>
|
218
|
+
<%= ui.icon(icon_path, class: component.style(:menu, :item, :icon)) %>
|
219
|
+
<% else %>
|
220
|
+
<span class="<%= component.style(:menu, :item, :icon) %>"></span>
|
221
|
+
<% end %>
|
222
|
+
<% if item[:block] %>
|
223
|
+
<%= capture(&item[:block]) %>
|
224
|
+
<% else %>
|
225
|
+
<%= item[:name] %>
|
226
|
+
<% end %>
|
227
|
+
</span>
|
228
|
+
<% else %>
|
229
|
+
<% if item[:block] %>
|
230
|
+
<%= capture(&item[:block]) %>
|
231
|
+
<% else %>
|
232
|
+
<%= item[:name] %>
|
233
|
+
<% end %>
|
234
|
+
<% end %>
|
235
|
+
<% end %>
|
236
|
+
<% elsif item[:type] == :button_tag %>
|
237
|
+
<% button_options = item[:options].dup %>
|
238
|
+
<% icon_path = item[:icon] %>
|
239
|
+
<% button_class = [component.style(:menu, :item, :root), button_options.delete(:class)].compact.join(" ") %>
|
240
|
+
|
241
|
+
<% # Add deferred close action %>
|
242
|
+
<% button_options[:data] ||= {} %>
|
243
|
+
<% existing_action = button_options[:data][:action] || button_options.delete("data-action") %>
|
244
|
+
<% button_options[:data][:action] = [existing_action, "click->dropdown#closeDeferred"].compact.join(" ") %>
|
245
|
+
|
246
|
+
<% # Handle onclick attribute if present %>
|
247
|
+
<% if button_options[:onclick] %>
|
248
|
+
<% original_onclick = button_options[:onclick] %>
|
249
|
+
<% button_options[:onclick] = "#{original_onclick}; setTimeout(() => { this.closest('[data-controller=\"dropdown\"]').querySelector('[data-dropdown-target=\"menu\"]').classList.add('hidden'); }, 50);" %>
|
250
|
+
<% end %>
|
251
|
+
|
252
|
+
<%= button_tag button_options.merge(
|
253
|
+
class: button_class,
|
254
|
+
role: "menuitem"
|
255
|
+
) do %>
|
256
|
+
<% if has_any_icon %>
|
257
|
+
<span class="<%= component.style(:menu, :item, :label) %>">
|
258
|
+
<% if icon_path %>
|
259
|
+
<%= ui.icon(icon_path, class: component.style(:menu, :item, :icon)) %>
|
260
|
+
<% else %>
|
261
|
+
<span class="<%= component.style(:menu, :item, :icon) %>"></span>
|
262
|
+
<% end %>
|
263
|
+
<% if item[:block] %>
|
264
|
+
<%= capture(&item[:block]) %>
|
265
|
+
<% else %>
|
266
|
+
<%= item[:name] %>
|
267
|
+
<% end %>
|
268
|
+
</span>
|
269
|
+
<% else %>
|
270
|
+
<% if item[:block] %>
|
271
|
+
<%= capture(&item[:block]) %>
|
272
|
+
<% else %>
|
273
|
+
<%= item[:name] %>
|
274
|
+
<% end %>
|
275
|
+
<% end %>
|
276
|
+
<% end %>
|
277
|
+
<% end %>
|
278
|
+
<% end %>
|
279
|
+
</div>
|
280
|
+
</div>
|
281
|
+
<% end %>
|
282
|
+
</div>
|
@@ -1,3 +1,34 @@
|
|
1
|
-
|
2
|
-
<%=
|
3
|
-
<%
|
1
|
+
<div class="<%= component.style(:wrapper) %>">
|
2
|
+
<div class="<%= component.style(:header) %>" x-data="{ open: false }">
|
3
|
+
<% if field_id %>
|
4
|
+
<%= form.label field_id, t("activerecord.attributes.#{form.object_name}.#{field_id}") %>
|
5
|
+
<% end %>
|
6
|
+
<% if options[:hint] %>
|
7
|
+
<div class="relative">
|
8
|
+
<div class="<%= component.style(:hint, :trigger) %>" @mouseover="open = true" @mouseover.away = "open = false">
|
9
|
+
Hilfe
|
10
|
+
</div>
|
11
|
+
<div x-show="open"
|
12
|
+
class="<%= component.style(:hint, :content) %>"
|
13
|
+
style="top: 32px; right: -10px; width: 200px; max-width: 400px; display: none;"
|
14
|
+
x-transition:enter="transition duration-200 transform ease-out"
|
15
|
+
x-transition:enter-start="scale-75"
|
16
|
+
x-transition:leave="transition duration-100 transform ease-in"
|
17
|
+
x-transition:leave-end="opacity-0 scale-90"
|
18
|
+
>
|
19
|
+
<%= options[:hint].is_a?(String) ? options[:hint] : t("activerecord.hints.#{form.object_name}.#{field_id}") %>
|
20
|
+
</div>
|
21
|
+
</div>
|
22
|
+
<% end %>
|
23
|
+
</div>
|
24
|
+
<div class="<%= component.style(:content) %>">
|
25
|
+
<div class="grid grid-cols-<%= options[:columns] || 1 %> gap-2 p-[1px]">
|
26
|
+
<%= yield %>
|
27
|
+
</div>
|
28
|
+
<% if field_id && form.object.errors.include?(field_id) %>
|
29
|
+
<div class="<%= component.style(:error) %>">
|
30
|
+
<%= form.object.errors[field_id].join(", ") %>
|
31
|
+
</div>
|
32
|
+
<% end %>
|
33
|
+
</div>
|
34
|
+
</div>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: okonomi_ui_kit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Okonomi GmbH
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-08-
|
11
|
+
date: 2025-08-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -1355,6 +1355,7 @@ files:
|
|
1355
1355
|
- app/assets/stylesheets/okonomi_ui_kit/application.css
|
1356
1356
|
- app/assets/stylesheets/okonomi_ui_kit/application.tailwind.css
|
1357
1357
|
- app/controllers/okonomi_ui_kit/application_controller.rb
|
1358
|
+
- app/helpers/okonomi_ui_kit/CLAUDE.md
|
1358
1359
|
- app/helpers/okonomi_ui_kit/application_helper.rb
|
1359
1360
|
- app/helpers/okonomi_ui_kit/attribute_section_helper.rb
|
1360
1361
|
- app/helpers/okonomi_ui_kit/component.rb
|
@@ -1367,6 +1368,7 @@ files:
|
|
1367
1368
|
- app/helpers/okonomi_ui_kit/components/button_to.rb
|
1368
1369
|
- app/helpers/okonomi_ui_kit/components/code.rb
|
1369
1370
|
- app/helpers/okonomi_ui_kit/components/confirmation_modal.rb
|
1371
|
+
- app/helpers/okonomi_ui_kit/components/dropdown_button.rb
|
1370
1372
|
- app/helpers/okonomi_ui_kit/components/forms.rb
|
1371
1373
|
- app/helpers/okonomi_ui_kit/components/forms/check_box_with_label.rb
|
1372
1374
|
- app/helpers/okonomi_ui_kit/components/forms/collection_select.rb
|
@@ -1393,6 +1395,8 @@ files:
|
|
1393
1395
|
- app/helpers/okonomi_ui_kit/components/link_to.rb
|
1394
1396
|
- app/helpers/okonomi_ui_kit/components/navigation.rb
|
1395
1397
|
- app/helpers/okonomi_ui_kit/components/page.rb
|
1398
|
+
- app/helpers/okonomi_ui_kit/components/page_header.rb
|
1399
|
+
- app/helpers/okonomi_ui_kit/components/page_section.rb
|
1396
1400
|
- app/helpers/okonomi_ui_kit/components/table.rb
|
1397
1401
|
- app/helpers/okonomi_ui_kit/components/typography.rb
|
1398
1402
|
- app/helpers/okonomi_ui_kit/config.rb
|
@@ -1419,6 +1423,7 @@ files:
|
|
1419
1423
|
- app/views/okonomi/components/breadcrumbs/_breadcrumbs.html.erb
|
1420
1424
|
- app/views/okonomi/components/code/_code.html.erb
|
1421
1425
|
- app/views/okonomi/components/confirmation_modal/_confirmation_modal.html.erb
|
1426
|
+
- app/views/okonomi/components/dropdown_button/_dropdown_button.html.erb
|
1422
1427
|
- app/views/okonomi/components/forms/check_box_with_label/_check_box_with_label.html.erb
|
1423
1428
|
- app/views/okonomi/components/forms/field/_field.html.erb
|
1424
1429
|
- app/views/okonomi/components/forms/field_set/_field_set.html.erb
|
@@ -1427,14 +1432,14 @@ files:
|
|
1427
1432
|
- app/views/okonomi/components/navigation/_link.html.erb
|
1428
1433
|
- app/views/okonomi/components/navigation/_navigation.html.erb
|
1429
1434
|
- app/views/okonomi/components/page/_page.html.erb
|
1435
|
+
- app/views/okonomi/components/page_header/_page_header.html.erb
|
1436
|
+
- app/views/okonomi/components/page_section/_page_section.html.erb
|
1430
1437
|
- app/views/okonomi/components/table/_table.html.erb
|
1431
1438
|
- app/views/okonomi/components/typography/_typography.html.erb
|
1432
1439
|
- app/views/okonomi/forms/tailwind/_checkbox_label.html.erb
|
1433
|
-
- app/views/okonomi/forms/tailwind/_field.html.erb
|
1434
1440
|
- app/views/okonomi/forms/tailwind/_multi_select.html.erb
|
1435
1441
|
- app/views/okonomi/forms/tailwind/_radio_button.html.erb
|
1436
1442
|
- app/views/okonomi/forms/tailwind/_upload_field.html.erb
|
1437
|
-
- app/views/okonomi/page_builder/_page.html.erb
|
1438
1443
|
- app/views/okonomi/tables/_table.html.erb
|
1439
1444
|
- config/importmap.rb
|
1440
1445
|
- config/routes.rb
|
@@ -1443,13 +1448,13 @@ files:
|
|
1443
1448
|
- lib/okonomi_ui_kit/engine.rb
|
1444
1449
|
- lib/okonomi_ui_kit/version.rb
|
1445
1450
|
- lib/tasks/okonomi_ui_kit_tasks.rake
|
1446
|
-
homepage: https://
|
1451
|
+
homepage: https://github.com/andre-l-github/okonomi_ui_kit
|
1447
1452
|
licenses:
|
1448
1453
|
- MIT
|
1449
1454
|
metadata:
|
1450
|
-
homepage_uri: https://
|
1451
|
-
source_code_uri: https://
|
1452
|
-
changelog_uri: https://
|
1455
|
+
homepage_uri: https://github.com/andre-l-github/okonomi_ui_kit
|
1456
|
+
source_code_uri: https://github.com/andre-l-github/okonomi_ui_kit
|
1457
|
+
changelog_uri: https://github.com/andre-l-github/okonomi_ui_kit
|
1453
1458
|
post_install_message:
|
1454
1459
|
rdoc_options: []
|
1455
1460
|
require_paths:
|
@@ -1,34 +0,0 @@
|
|
1
|
-
<div class="<%= component.style(:wrapper) %>">
|
2
|
-
<div class="<%= component.style(:header) %>" x-data="{ open: false }">
|
3
|
-
<% if field_id %>
|
4
|
-
<%= form.label field_id, t("activerecord.attributes.#{form.object_name}.#{field_id}") %>
|
5
|
-
<% end %>
|
6
|
-
<% if options[:hint] %>
|
7
|
-
<div class="relative">
|
8
|
-
<div class="<%= component.style(:hint, :trigger) %>" @mouseover="open = true" @mouseover.away = "open = false">
|
9
|
-
Hilfe
|
10
|
-
</div>
|
11
|
-
<div x-show="open"
|
12
|
-
class="<%= component.style(:hint, :content) %>"
|
13
|
-
style="top: 32px; right: -10px; width: 200px; max-width: 400px; display: none;"
|
14
|
-
x-transition:enter="transition duration-200 transform ease-out"
|
15
|
-
x-transition:enter-start="scale-75"
|
16
|
-
x-transition:leave="transition duration-100 transform ease-in"
|
17
|
-
x-transition:leave-end="opacity-0 scale-90"
|
18
|
-
>
|
19
|
-
<%= options[:hint].is_a?(String) ? options[:hint] : t("activerecord.hints.#{form.object_name}.#{field_id}") %>
|
20
|
-
</div>
|
21
|
-
</div>
|
22
|
-
<% end %>
|
23
|
-
</div>
|
24
|
-
<div class="<%= component.style(:content) %>">
|
25
|
-
<div class="grid grid-cols-<%= options[:columns] || 1 %> gap-2">
|
26
|
-
<%= yield %>
|
27
|
-
</div>
|
28
|
-
<% if field_id && form.object.errors.include?(field_id) %>
|
29
|
-
<div class="<%= component.style(:error) %>">
|
30
|
-
<%= form.object.errors[field_id].join(", ") %>
|
31
|
-
</div>
|
32
|
-
<% end %>
|
33
|
-
</div>
|
34
|
-
</div>
|