okonomi_ui_kit 0.1.6 → 0.1.8

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +163 -7
  3. data/app/assets/builds/okonomi_ui_kit/application.tailwind.css +292 -4
  4. data/app/helpers/okonomi_ui_kit/component.rb +81 -0
  5. data/app/helpers/okonomi_ui_kit/components/badge.rb +21 -16
  6. data/app/helpers/okonomi_ui_kit/components/breadcrumbs.rb +69 -0
  7. data/app/helpers/okonomi_ui_kit/components/button_base.rb +56 -0
  8. data/app/helpers/okonomi_ui_kit/components/button_tag.rb +23 -0
  9. data/app/helpers/okonomi_ui_kit/components/button_to.rb +23 -0
  10. data/app/helpers/okonomi_ui_kit/components/code.rb +73 -0
  11. data/app/helpers/okonomi_ui_kit/components/icon.rb +36 -0
  12. data/app/helpers/okonomi_ui_kit/components/link_to.rb +23 -0
  13. data/app/helpers/okonomi_ui_kit/components/page.rb +247 -0
  14. data/app/helpers/okonomi_ui_kit/components/table.rb +207 -0
  15. data/app/helpers/okonomi_ui_kit/components/typography.rb +29 -3
  16. data/app/helpers/okonomi_ui_kit/config.rb +20 -0
  17. data/app/helpers/okonomi_ui_kit/form_builder.rb +2 -2
  18. data/app/helpers/okonomi_ui_kit/t_w_merge.rb +108 -0
  19. data/app/helpers/okonomi_ui_kit/theme.rb +3 -26
  20. data/app/helpers/okonomi_ui_kit/ui_helper.rb +0 -40
  21. data/app/views/okonomi/components/breadcrumbs/_breadcrumbs.html.erb +46 -0
  22. data/app/views/okonomi/components/code/_code.html.erb +1 -0
  23. data/app/views/okonomi/components/icon/_icon.html.erb +38 -0
  24. data/app/views/okonomi/components/page/_page.html.erb +5 -0
  25. data/app/views/okonomi/components/table/_table.html.erb +3 -0
  26. data/app/views/okonomi/forms/tailwind/_upload_field.html.erb +2 -2
  27. data/app/views/okonomi/modals/_confirmation_modal.html.erb +2 -2
  28. data/app/views/okonomi/navigation/_link.html.erb +1 -1
  29. data/lib/okonomi_ui_kit/engine.rb +0 -5
  30. data/lib/okonomi_ui_kit/version.rb +1 -1
  31. metadata +18 -7
  32. data/app/helpers/okonomi_ui_kit/badge_helper.rb +0 -23
  33. data/app/helpers/okonomi_ui_kit/breadcrumbs_helper.rb +0 -60
  34. data/app/helpers/okonomi_ui_kit/icon_helper.rb +0 -39
  35. data/app/helpers/okonomi_ui_kit/page_builder_helper.rb +0 -217
  36. data/app/helpers/okonomi_ui_kit/table_helper.rb +0 -158
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.6
4
+ version: 0.1.8
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-07-31 00:00:00.000000000 Z
11
+ date: 2025-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -1357,18 +1357,24 @@ files:
1357
1357
  - app/controllers/okonomi_ui_kit/application_controller.rb
1358
1358
  - app/helpers/okonomi_ui_kit/application_helper.rb
1359
1359
  - app/helpers/okonomi_ui_kit/attribute_section_helper.rb
1360
- - app/helpers/okonomi_ui_kit/badge_helper.rb
1361
- - app/helpers/okonomi_ui_kit/breadcrumbs_helper.rb
1362
1360
  - app/helpers/okonomi_ui_kit/component.rb
1363
1361
  - app/helpers/okonomi_ui_kit/components/alert.rb
1364
1362
  - app/helpers/okonomi_ui_kit/components/badge.rb
1363
+ - app/helpers/okonomi_ui_kit/components/breadcrumbs.rb
1364
+ - app/helpers/okonomi_ui_kit/components/button_base.rb
1365
+ - app/helpers/okonomi_ui_kit/components/button_tag.rb
1366
+ - app/helpers/okonomi_ui_kit/components/button_to.rb
1367
+ - app/helpers/okonomi_ui_kit/components/code.rb
1368
+ - app/helpers/okonomi_ui_kit/components/icon.rb
1369
+ - app/helpers/okonomi_ui_kit/components/link_to.rb
1370
+ - app/helpers/okonomi_ui_kit/components/page.rb
1371
+ - app/helpers/okonomi_ui_kit/components/table.rb
1365
1372
  - app/helpers/okonomi_ui_kit/components/typography.rb
1373
+ - app/helpers/okonomi_ui_kit/config.rb
1366
1374
  - app/helpers/okonomi_ui_kit/form_builder.rb
1367
- - app/helpers/okonomi_ui_kit/icon_helper.rb
1368
1375
  - app/helpers/okonomi_ui_kit/navigation_helper.rb
1369
- - app/helpers/okonomi_ui_kit/page_builder_helper.rb
1370
1376
  - app/helpers/okonomi_ui_kit/svg_icons.rb
1371
- - app/helpers/okonomi_ui_kit/table_helper.rb
1377
+ - app/helpers/okonomi_ui_kit/t_w_merge.rb
1372
1378
  - app/helpers/okonomi_ui_kit/theme.rb
1373
1379
  - app/helpers/okonomi_ui_kit/theme_helper.rb
1374
1380
  - app/helpers/okonomi_ui_kit/ui_helper.rb
@@ -1386,6 +1392,11 @@ files:
1386
1392
  - app/views/layouts/okonomi_ui_kit/application.html.erb
1387
1393
  - app/views/okonomi/attribute_sections/_section.html.erb
1388
1394
  - app/views/okonomi/components/alert/_alert.html.erb
1395
+ - app/views/okonomi/components/breadcrumbs/_breadcrumbs.html.erb
1396
+ - app/views/okonomi/components/code/_code.html.erb
1397
+ - app/views/okonomi/components/icon/_icon.html.erb
1398
+ - app/views/okonomi/components/page/_page.html.erb
1399
+ - app/views/okonomi/components/table/_table.html.erb
1389
1400
  - app/views/okonomi/components/typography/_typography.html.erb
1390
1401
  - app/views/okonomi/forms/tailwind/_checkbox_label.html.erb
1391
1402
  - app/views/okonomi/forms/tailwind/_field.html.erb
@@ -1,23 +0,0 @@
1
- module OkonomiUiKit
2
- module BadgeHelper
3
- def badge(text, severity = :default, **options)
4
- color_classes = case severity.to_sym
5
- when :success
6
- "bg-green-100 text-green-800"
7
- when :danger
8
- "bg-red-100 text-red-800"
9
- when :info
10
- "bg-blue-100 text-blue-800"
11
- when :warning
12
- "bg-yellow-100 text-yellow-800"
13
- else
14
- "bg-gray-100 text-gray-800"
15
- end
16
-
17
- base_classes = "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
18
- full_classes = "#{base_classes} #{color_classes} #{options[:class] || ''}".strip
19
-
20
- tag.span(text, class: full_classes, **options.except(:class))
21
- end
22
- end
23
- end
@@ -1,60 +0,0 @@
1
- module OkonomiUiKit
2
- module BreadcrumbsHelper
3
- def breadcrumbs
4
- content_tag(:nav, class: "flex", aria: { label: "Breadcrumb" }) do
5
- content_tag(:ol, class: "flex items-center space-x-4", role: "list") do
6
- builder = BreadcrumbBuilder.new(self)
7
- yield builder
8
- safe_join(builder.items)
9
- end
10
- end
11
- end
12
-
13
- class BreadcrumbBuilder
14
- attr_reader :items
15
-
16
- def initialize(view)
17
- @view = view
18
- @items = []
19
- @first = true
20
- end
21
-
22
- def link(label, url, icon: nil, current: false)
23
- if @first
24
- @items << @view.content_tag(:li) do
25
- @view.content_tag(:div) do
26
- if icon.present?
27
- @view.link_to(url, class: "text-gray-400 hover:text-gray-500") do
28
- @view.svg_icon(icon, class: "size-5") + @view.content_tag(:span, label, class: "sr-only")
29
- end
30
- else
31
- @view.content_tag(:div, class: "flex items-center") do
32
- @view.link_to(label, url, class: breadcrumb_classes(current, first: true), aria: (current ? { current: "page" } : {}))
33
- end
34
- end
35
- end
36
- end
37
- @first = false
38
- else
39
- @items << @view.content_tag(:li) do
40
- @view.content_tag(:div, class: "flex items-center") do
41
- chevron + @view.link_to(label, url, class: breadcrumb_classes(current), aria: (current ? { current: "page" } : {}))
42
- end
43
- end
44
- end
45
- end
46
-
47
- private
48
-
49
- def chevron
50
- @view.svg_icon("heroicons/solid/chevron-right", class: "size-5 shrink-0 text-gray-400")
51
- end
52
-
53
- def breadcrumb_classes(current, first: false)
54
- base = "text-sm font-medium"
55
- base = "#{base} ml-4" unless first
56
- current ? "#{base} text-gray-500" : "#{base} text-gray-500 hover:text-gray-700"
57
- end
58
- end
59
- end
60
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OkonomiUiKit
4
- module IconHelper
5
- def icon_tag(icon, variant = :outlined, options = {})
6
- if icon.is_a?(Icon)
7
- doc = Nokogiri::HTML::DocumentFragment.parse icon.send(:"content_#{variant}")
8
- svg = doc.at_css 'svg'
9
-
10
- svg['class'] = options[:class] if options[:class].present?
11
- svg['style'] = options[:style] if options[:style].present?
12
-
13
- svg['width'] = options[:width] if options[:width].present?
14
- svg['height'] = options[:height] if options[:height].present?
15
-
16
- raw doc
17
- else
18
- svg_icon(icon, options)
19
- end
20
- end
21
-
22
- def svg_icon(name, options = {})
23
- if OkonomiUiKit::SvgIcons.exist?(name)
24
- doc = Nokogiri::HTML::DocumentFragment.parse OkonomiUiKit::SvgIcons.read(name)
25
- svg = doc.at_css 'svg'
26
-
27
- svg['class'] = options[:class] if options[:class].present?
28
- svg['style'] = options[:style] if options[:style].present?
29
-
30
- svg['width'] = options[:width] if options[:width].present?
31
- svg['height'] = options[:height] if options[:height].present?
32
- else
33
- doc = "<!-- SVG #{name} not found -->"
34
- end
35
-
36
- raw doc
37
- end
38
- end
39
- end
@@ -1,217 +0,0 @@
1
- module OkonomiUiKit
2
- module PageBuilderHelper
3
- def page(**options, &block)
4
- builder = PageBuilder.new(self)
5
-
6
- render 'okonomi/page_builder/page', builder: builder, options: options, &block
7
- end
8
-
9
- class PageBuilder
10
- include ActionView::Helpers::TagHelper
11
- include ActionView::Helpers::CaptureHelper
12
-
13
- def initialize(template)
14
- @template = template
15
- end
16
-
17
- def page_header(**options, &block)
18
- header_builder = PageHeaderBuilder.new(@template)
19
- yield(header_builder) if block_given?
20
- header_builder.render
21
- end
22
-
23
- def section(**options, &block)
24
- section_builder = SectionBuilder.new(@template)
25
- yield(section_builder) if block_given?
26
- section_builder.render
27
- end
28
-
29
- private
30
-
31
- def tag
32
- @template.tag
33
- end
34
-
35
- def capture(*args, &block)
36
- @template.capture(*args, &block)
37
- end
38
- end
39
-
40
- class PageHeaderBuilder
41
- include ActionView::Helpers::TagHelper
42
- include ActionView::Helpers::CaptureHelper
43
-
44
- def initialize(template)
45
- @template = template
46
- @breadcrumbs_content = nil
47
- @row_content = nil
48
- end
49
-
50
- def breadcrumbs(&block)
51
- @breadcrumbs_content = @template.breadcrumbs(&block)
52
- end
53
-
54
- def row(&block)
55
- row_builder = PageHeaderRowBuilder.new(@template)
56
- yield(row_builder) if block_given?
57
- @row_content = row_builder.render
58
- end
59
-
60
- def render
61
- content = []
62
- content << @breadcrumbs_content if @breadcrumbs_content
63
- content << @row_content if @row_content
64
-
65
- tag.div(class: "flex flex-col gap-2") do
66
- @template.safe_join(content.compact)
67
- end
68
- end
69
-
70
- private
71
-
72
- def tag
73
- @template.tag
74
- end
75
-
76
- def capture(*args, &block)
77
- @template.capture(*args, &block)
78
- end
79
- end
80
-
81
- class PageHeaderRowBuilder
82
- include ActionView::Helpers::TagHelper
83
- include ActionView::Helpers::CaptureHelper
84
-
85
- def initialize(template)
86
- @template = template
87
- @title_content = nil
88
- @actions_content = nil
89
- end
90
-
91
- def title(text, **options)
92
- @title_content = tag.h1(text, class: "text-2xl font-bold leading-7 text-gray-900 truncate sm:text-3xl sm:tracking-tight")
93
- end
94
-
95
- def actions(&block)
96
- @actions_content = tag.div(class: "mt-4 flex md:ml-4 md:mt-0 gap-2") do
97
- capture(&block) if block_given?
98
- end
99
- end
100
-
101
- def render
102
- tag.div(class: "flex w-full justify-between items-center") do
103
- content = []
104
- content << @title_content if @title_content
105
- content << @actions_content if @actions_content
106
- @template.safe_join(content.compact)
107
- end
108
- end
109
-
110
- private
111
-
112
- def tag
113
- @template.tag
114
- end
115
-
116
- def capture(*args, &block)
117
- @template.capture(*args, &block)
118
- end
119
- end
120
-
121
- class SectionBuilder
122
- include ActionView::Helpers::TagHelper
123
- include ActionView::Helpers::CaptureHelper
124
-
125
- def initialize(template)
126
- @template = template
127
- @title_content = nil
128
- @subtitle_content = nil
129
- @actions_content = nil
130
- @body_content = nil
131
- end
132
-
133
- def title(text, **options)
134
- @title_content = tag.h3(text, class: "text-base/7 font-semibold text-gray-900")
135
- end
136
-
137
- def subtitle(text, **options)
138
- @subtitle_content = tag.p(text, class: "mt-1 max-w-2xl text-sm/6 text-gray-500")
139
- end
140
-
141
- def actions(&block)
142
- @actions_content = tag.div(class: "mt-4 flex md:ml-4 md:mt-0") do
143
- capture(&block) if block_given?
144
- end
145
- end
146
-
147
- def body(&block)
148
- @body_content = tag.div do
149
- tag.dl(class: "divide-y divide-gray-100") do
150
- capture(&block) if block_given?
151
- end
152
- end
153
- end
154
-
155
- def attribute(label, value = nil, **options, &block)
156
- content = if block_given?
157
- capture(&block)
158
- elsif value.respond_to?(:call)
159
- value.call
160
- else
161
- value
162
- end
163
-
164
- tag.div(class: "py-6 sm:grid sm:grid-cols-3 sm:gap-4") do
165
- dt_content = tag.dt(label, class: "text-sm font-medium text-gray-900")
166
- dd_content = tag.dd(content, class: "mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0")
167
-
168
- dt_content + dd_content
169
- end
170
- end
171
-
172
- def render
173
- tag.div(class: "overflow-hidden bg-white") do
174
- header_content = build_header
175
- content_parts = []
176
- content_parts << header_content if header_content.present?
177
- content_parts << @body_content if @body_content
178
- @template.safe_join(content_parts.compact)
179
- end
180
- end
181
-
182
- private
183
-
184
- def build_header
185
- return nil unless @title_content || @subtitle_content || @actions_content
186
-
187
- tag.div(class: "py-6") do
188
- if @actions_content
189
- tag.div(class: "flex w-full justify-between items-start") do
190
- title_section = tag.div do
191
- content_parts = []
192
- content_parts << @title_content if @title_content
193
- content_parts << @subtitle_content if @subtitle_content
194
- @template.safe_join(content_parts.compact)
195
- end
196
-
197
- title_section + @actions_content
198
- end
199
- else
200
- content_parts = []
201
- content_parts << @title_content if @title_content
202
- content_parts << @subtitle_content if @subtitle_content
203
- @template.safe_join(content_parts.compact)
204
- end
205
- end
206
- end
207
-
208
- def tag
209
- @template.tag
210
- end
211
-
212
- def capture(*args, &block)
213
- @template.capture(*args, &block)
214
- end
215
- end
216
- end
217
- end
@@ -1,158 +0,0 @@
1
- module OkonomiUiKit
2
- module TableHelper
3
- def table(**options, &block)
4
- builder = TableBuilder.new(self)
5
-
6
- render 'okonomi/tables/table', builder: builder, options: options, &block
7
- end
8
-
9
- class TableBuilder
10
- include ActionView::Helpers::TagHelper
11
- include ActionView::Helpers::CaptureHelper
12
-
13
- def initialize(template)
14
- @template = template
15
- @current_row_cells = []
16
- @in_header = false
17
- @in_body = false
18
- end
19
-
20
- def head(&block)
21
- @in_header = true
22
- @in_body = false
23
- result = tag.thead(&block)
24
- @in_header = false
25
- result
26
- end
27
-
28
- def body(&block)
29
- @in_header = false
30
- @in_body = true
31
- result = tag.tbody(class: "divide-y divide-gray-200 bg-white", &block)
32
- @in_body = false
33
- result
34
- end
35
-
36
- def tr(&block)
37
- @current_row_cells = []
38
-
39
- # Collect all cells first
40
- yield if block_given?
41
-
42
- # Now render each cell with proper first/last detection
43
- rendered_cells = @current_row_cells.map.with_index do |cell, index|
44
- is_first = index == 0
45
- is_last = index == @current_row_cells.length - 1
46
-
47
- if cell[:type] == :th
48
- render_th(cell, is_first, is_last)
49
- else
50
- render_td(cell, is_first, is_last)
51
- end
52
- end
53
-
54
- result = tag.tr do
55
- @template.safe_join(rendered_cells)
56
- end
57
-
58
- @current_row_cells = []
59
- result
60
- end
61
-
62
- def th(scope: "col", align: :left, **options, &block)
63
- content = capture(&block) if block_given?
64
-
65
- # Store cell data for later processing in tr
66
- cell = { type: :th, scope: scope, align: align, options: options, content: content }
67
- @current_row_cells << cell
68
-
69
- # Return empty string for now, actual rendering happens in tr
70
- ""
71
- end
72
-
73
- def td(align: :left, **options, &block)
74
- content = capture(&block) if block_given?
75
-
76
- # Store cell data for later processing in tr
77
- cell = { type: :td, align: align, options: options, content: content }
78
- @current_row_cells << cell
79
-
80
- # Return empty string for now, actual rendering happens in tr
81
- ""
82
- end
83
-
84
-
85
- def empty_state(title: "No records found", icon: "heroicons/outline/document", colspan: nil, &block)
86
- content = if block_given?
87
- capture(&block)
88
- else
89
- tag.div(class: "text-center py-8") do
90
- icon_content = @template.svg_icon(icon, class: "mx-auto h-12 w-12 text-gray-400")
91
-
92
- icon_content + tag.p(title, class: "mt-2 text-sm font-medium text-gray-900") +
93
- tag.p("Get started by creating a new record.", class: "mt-1 text-sm text-gray-500")
94
- end
95
- end
96
-
97
- tr do
98
- td(colspan: colspan, class: "text-center py-8 text-gray-500") do
99
- content
100
- end
101
- end
102
- end
103
-
104
- private
105
-
106
- def tag
107
- @template.tag
108
- end
109
-
110
- def capture(*args, &block)
111
- @template.capture(*args, &block)
112
- end
113
-
114
- def render_th(cell, is_first, is_last)
115
- align_class = alignment_class(cell[:align])
116
-
117
- if is_first
118
- full_class = "py-3.5 pr-3 #{align_class} text-sm font-semibold text-gray-900 #{cell[:options][:class] || ""}".strip
119
- elsif is_last
120
- full_class = "relative py-3.5 #{align_class} text-sm font-semibold text-gray-900 #{cell[:options][:class] || ""}".strip
121
- else
122
- full_class = "pl-3 pr-3 py-3.5 #{align_class} text-sm font-semibold text-gray-900 #{cell[:options][:class] || ""}".strip
123
- end
124
-
125
- options = cell[:options].except(:class)
126
- tag.th(cell[:content], scope: cell[:scope], class: full_class, **options)
127
- end
128
-
129
- def render_td(cell, is_first, is_last)
130
- align_class = alignment_class(cell[:align])
131
-
132
- if is_first
133
- full_class = "py-4 pr-3 #{align_class} text-sm font-medium whitespace-nowrap text-gray-900 #{cell[:options][:class] || ""}".strip
134
- elsif is_last
135
- full_class = "relative py-4 #{align_class} text-sm font-medium whitespace-nowrap #{cell[:options][:class] || ""}".strip
136
- else
137
- full_class = "pl-3 pr-3 py-4 #{align_class} text-sm whitespace-nowrap text-gray-500 #{cell[:options][:class] || ""}".strip
138
- end
139
-
140
- options = cell[:options].except(:class)
141
- tag.td(cell[:content], class: full_class, **options)
142
- end
143
-
144
- def alignment_class(align)
145
- case align.to_sym
146
- when :left
147
- "text-left"
148
- when :center
149
- "text-center"
150
- when :right
151
- "text-right"
152
- else
153
- "text-left"
154
- end
155
- end
156
- end
157
- end
158
- end