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.
@@ -0,0 +1,207 @@
1
+ module OkonomiUiKit
2
+ module Components
3
+ class Table < OkonomiUiKit::Component
4
+ def render(options = {}, &block)
5
+ options = options.with_indifferent_access
6
+ variant = (options.delete(:variant) || :default).to_sym
7
+
8
+ builder = TableBuilder.new(view, theme, self, variant)
9
+ view.render(template_path, builder: builder, options: options, &block)
10
+ end
11
+
12
+ register_styles :default do
13
+ {
14
+ default: {
15
+ body: {
16
+ base: "divide-y divide-gray-200 bg-white"
17
+ },
18
+ th: {
19
+ base: "text-sm font-semibold text-gray-900",
20
+ first: "py-3.5 pr-3",
21
+ last: "relative py-3.5",
22
+ middle: "pl-3 pr-3 py-3.5"
23
+ },
24
+ td: {
25
+ base: "text-sm whitespace-nowrap",
26
+ first: "py-4 pr-3 font-medium text-gray-900",
27
+ last: "relative py-4 font-medium",
28
+ middle: "pl-3 pr-3 py-4 text-gray-500"
29
+ },
30
+ alignment: {
31
+ left: "text-left",
32
+ center: "text-center",
33
+ right: "text-right"
34
+ },
35
+ empty_state: {
36
+ wrapper: "text-center py-8",
37
+ icon: "mx-auto h-12 w-12 text-gray-400",
38
+ title: "mt-2 text-sm font-medium text-gray-900",
39
+ subtitle: "mt-1 text-sm text-gray-500",
40
+ cell: "text-center py-8 text-gray-500"
41
+ }
42
+ }
43
+ }
44
+ end
45
+ end
46
+
47
+ class TableBuilder
48
+ include ActionView::Helpers::TagHelper
49
+ include ActionView::Helpers::CaptureHelper
50
+
51
+ def initialize(template, theme, style_provider, variant = :default)
52
+ @template = template
53
+ @theme = theme
54
+ @style_provider = style_provider
55
+ @variant = variant
56
+ @current_row_cells = []
57
+ @in_header = false
58
+ @in_body = false
59
+ end
60
+
61
+ def head(&block)
62
+ @in_header = true
63
+ @in_body = false
64
+ result = tag.thead(&block)
65
+ @in_header = false
66
+ result
67
+ end
68
+
69
+ def body(&block)
70
+ @in_header = false
71
+ @in_body = true
72
+ result = tag.tbody(class: style(:body, :base), &block)
73
+ @in_body = false
74
+ result
75
+ end
76
+
77
+ def tr(&block)
78
+ @current_row_cells = []
79
+
80
+ # Collect all cells first
81
+ yield if block_given?
82
+
83
+ # Now render each cell with proper first/last detection
84
+ rendered_cells = @current_row_cells.map.with_index do |cell, index|
85
+ is_first = index == 0
86
+ is_last = index == @current_row_cells.length - 1
87
+
88
+ if cell[:type] == :th
89
+ render_th(cell, is_first, is_last)
90
+ else
91
+ render_td(cell, is_first, is_last)
92
+ end
93
+ end
94
+
95
+ result = tag.tr do
96
+ @template.safe_join(rendered_cells)
97
+ end
98
+
99
+ @current_row_cells = []
100
+ result
101
+ end
102
+
103
+ def th(scope: "col", align: :left, **options, &block)
104
+ content = capture(&block) if block_given?
105
+
106
+ # Store cell data for later processing in tr
107
+ cell = { type: :th, scope: scope, align: align, options: options, content: content }
108
+ @current_row_cells << cell
109
+
110
+ # Return empty string for now, actual rendering happens in tr
111
+ ""
112
+ end
113
+
114
+ def td(align: :left, **options, &block)
115
+ content = capture(&block) if block_given?
116
+
117
+ # Store cell data for later processing in tr
118
+ cell = { type: :td, align: align, options: options, content: content }
119
+ @current_row_cells << cell
120
+
121
+ # Return empty string for now, actual rendering happens in tr
122
+ ""
123
+ end
124
+
125
+ def empty_state(title: "No records found", icon: "heroicons/outline/document", colspan: nil, &block)
126
+ content = if block_given?
127
+ capture(&block)
128
+ else
129
+ tag.div(class: style(:empty_state, :wrapper)) do
130
+ icon_content = if @template.respond_to?(:svg_icon)
131
+ @template.svg_icon(icon, class: style(:empty_state, :icon))
132
+ else
133
+ tag.div(class: style(:empty_state, :icon))
134
+ end
135
+
136
+ icon_content + tag.p(title, class: style(:empty_state, :title)) +
137
+ tag.p("Get started by creating a new record.", class: style(:empty_state, :subtitle))
138
+ end
139
+ end
140
+
141
+ tr do
142
+ td(colspan: colspan, class: style(:empty_state, :cell)) do
143
+ content
144
+ end
145
+ end
146
+ end
147
+
148
+ private
149
+
150
+ def tag
151
+ @template.tag
152
+ end
153
+
154
+ def capture(*args, &block)
155
+ @template.capture(*args, &block)
156
+ end
157
+
158
+ def render_th(cell, is_first, is_last)
159
+ align_class = style(:alignment, cell[:align]) || style(:alignment, :left)
160
+
161
+ position_class = if is_first
162
+ style(:th, :first)
163
+ elsif is_last
164
+ style(:th, :last)
165
+ else
166
+ style(:th, :middle)
167
+ end
168
+
169
+ classes = [
170
+ style(:th, :base),
171
+ position_class,
172
+ align_class,
173
+ cell[:options][:class]
174
+ ].compact.join(' ')
175
+
176
+ options = cell[:options].except(:class)
177
+ tag.th(cell[:content], scope: cell[:scope], class: classes, **options)
178
+ end
179
+
180
+ def render_td(cell, is_first, is_last)
181
+ align_class = style(:alignment, cell[:align]) || style(:alignment, :left)
182
+
183
+ position_class = if is_first
184
+ style(:td, :first)
185
+ elsif is_last
186
+ style(:td, :last)
187
+ else
188
+ style(:td, :middle)
189
+ end
190
+
191
+ classes = [
192
+ style(:td, :base),
193
+ position_class,
194
+ align_class,
195
+ cell[:options][:class]
196
+ ].compact.join(' ')
197
+
198
+ options = cell[:options].except(:class)
199
+ tag.td(cell[:content], class: classes, **options)
200
+ end
201
+
202
+ def style(*keys)
203
+ @style_provider.style(@variant, *keys)
204
+ end
205
+ end
206
+ end
207
+ end
@@ -20,10 +20,10 @@ module OkonomiUiKit
20
20
  variant = (options.delete(:variant) || 'body1').to_sym
21
21
  component = (TYPOGRAPHY_COMPONENTS[variant] || 'span').to_s
22
22
  color = (options.delete(:color) || 'default').to_sym
23
-
23
+
24
24
  classes = [
25
- theme.dig(:components, :typography, :variants, variant) || '',
26
- theme.dig(:components, :typography, :colors, color) || '',
25
+ style(:variants, variant) || '',
26
+ style(:colors, color) || '',
27
27
  options.delete(:class) || ''
28
28
  ].reject(&:blank?).join(' ')
29
29
 
@@ -37,6 +37,32 @@ module OkonomiUiKit
37
37
  &block
38
38
  )
39
39
  end
40
+
41
+ register_styles :default do
42
+ {
43
+ variants: {
44
+ body1: "text-base font-normal",
45
+ body2: "text-sm font-normal",
46
+ h1: "text-3xl font-bold",
47
+ h2: "text-2xl font-bold",
48
+ h3: "text-xl font-semibold",
49
+ h4: "text-lg font-semibold",
50
+ h5: "text-base font-semibold",
51
+ h6: "text-sm font-semibold"
52
+ },
53
+ colors: {
54
+ default: "text-default-700",
55
+ dark: "text-default-900",
56
+ muted: "text-default-500",
57
+ primary: "text-primary-600",
58
+ secondary: "text-secondary-600",
59
+ success: "text-success-600",
60
+ danger: "text-danger-600",
61
+ warning: "text-warning-600",
62
+ info: "text-info-600"
63
+ }
64
+ }
65
+ end
40
66
  end
41
67
  end
42
68
  end
@@ -0,0 +1,16 @@
1
+ module OkonomiUiKit
2
+ class Config
3
+ def self.register_styles(theme = :default, &block)
4
+ styles = block.call if block_given?
5
+
6
+ raise ArgumentError, "Styles must be a Hash" unless styles.is_a?(Hash)
7
+
8
+ styles_registry[theme] ||= {}
9
+ styles_registry[theme] = styles_registry[theme].deep_merge(styles)
10
+ end
11
+
12
+ def self.styles_registry
13
+ @styles_registry ||= {}
14
+ end
15
+ end
16
+ end
@@ -26,9 +26,9 @@ module OkonomiUiKit
26
26
  }
27
27
  },
28
28
  link: {
29
- root: "hover:cursor-pointer",
29
+ root: "hover:cursor-pointer text-sm",
30
30
  outlined: {
31
- root: "inline-flex border items-center justify-center px-4 py-2 font-medium focus:outline-none focus:ring-2 focus:ring-offset-2",
31
+ root: "inline-flex border items-center justify-center px-2 py-1 rounded-md font-medium focus:outline-none focus:ring-2 focus:ring-offset-2",
32
32
  colors: {
33
33
  default: "bg-white text-default-700 border-default-700 hover:bg-default-50",
34
34
  primary: "bg-white text-primary-600 border-primary-600 hover:bg-primary-50",
@@ -40,7 +40,7 @@ module OkonomiUiKit
40
40
  }
41
41
  },
42
42
  contained: {
43
- root: "inline-flex border items-center justify-center px-4 py-2 font-medium focus:outline-none focus:ring-2 focus:ring-offset-2",
43
+ root: "inline-flex border items-center justify-center px-2 py-1 rounded-md font-medium focus:outline-none focus:ring-2 focus:ring-offset-2",
44
44
  colors: {
45
45
  default: "border-default-700 bg-default-600 text-white hover:bg-default-700",
46
46
  primary: "border-primary-700 bg-primary-600 text-white hover:bg-primary-700",
@@ -26,42 +26,6 @@ module OkonomiUiKit
26
26
  @_okonomi_ui_kit_theme ||= OkonomiUiKit::Theme::DEFAULT_THEME
27
27
  end
28
28
 
29
- def link_to(name = nil, options = nil, html_options = nil, &block)
30
- html_options, options, name = options, name, block if block_given?
31
-
32
- html_options ||= {}
33
- html_options[:class] ||= ''
34
-
35
- variant = (html_options.delete(:variant) || 'text').to_sym
36
- color = (html_options.delete(:color) || 'default').to_sym
37
-
38
- html_options[:class] = button_class(variant:, color:, classes: html_options[:class])
39
-
40
- if block_given?
41
- @template.link_to(options, html_options, &block)
42
- else
43
- @template.link_to(name, options, html_options)
44
- end
45
- end
46
-
47
- def button_to(name = nil, options = nil, html_options = nil, &block)
48
- html_options, options, name = options, name, block if block_given?
49
-
50
- html_options ||= {}
51
- html_options[:class] ||= ''
52
-
53
- variant = (html_options.delete(:variant) || 'contained').to_sym
54
- color = (html_options.delete(:color) || 'default').to_sym
55
-
56
- html_options[:class] = button_class(variant:, color:, classes: html_options[:class])
57
-
58
- if block_given?
59
- @template.button_to(options, html_options, &block)
60
- else
61
- @template.button_to(name, options, html_options)
62
- end
63
- end
64
-
65
29
  def button_class(variant: 'contained', color: 'default', classes: '')
66
30
  [
67
31
  get_theme.dig(:components, :link, :root) || '',
@@ -71,10 +35,6 @@ module OkonomiUiKit
71
35
  ].join(' ')
72
36
  end
73
37
 
74
- def page(&block)
75
- @template.page(&block)
76
- end
77
-
78
38
  def confirmation_modal(title:, message:, confirm_text: "Confirm", cancel_text: "Cancel", variant: :warning, size: :md, **options, &block)
79
39
  modal_options = {
80
40
  title: title,
@@ -0,0 +1 @@
1
+ <pre<%= language ? " data-language=\"#{language}\"".html_safe : "" %> class="<%= classes %>"<%= tag.attributes(options) %>><code><%= content %></code></pre>
@@ -0,0 +1,5 @@
1
+ <div class="flex flex-col gap-8 p-8 <%= options[:class] || '' %>">
2
+ <% content = capture { yield(builder) } %>
3
+ <%= builder.render_content %>
4
+ <%= content if builder.render_content.blank? %>
5
+ </div>
@@ -0,0 +1,3 @@
1
+ <table class="min-w-full divide-y divide-gray-300" <%= tag.attributes(options) %>>
2
+ <%= yield builder %>
3
+ </table>
@@ -22,12 +22,9 @@ module OkonomiUiKit
22
22
  ActiveSupport.on_load(:action_view) do
23
23
  include OkonomiUiKit::ApplicationHelper
24
24
  include OkonomiUiKit::AttributeSectionHelper
25
- include OkonomiUiKit::BadgeHelper
26
25
  include OkonomiUiKit::BreadcrumbsHelper
27
26
  include OkonomiUiKit::IconHelper
28
27
  include OkonomiUiKit::NavigationHelper
29
- include OkonomiUiKit::PageBuilderHelper
30
- include OkonomiUiKit::TableHelper
31
28
  include OkonomiUiKit::UiHelper
32
29
 
33
30
  ActionView::Base.field_error_proc = ->(html_tag, _instance) { html_tag.html_safe }
@@ -1,3 +1,3 @@
1
1
  module OkonomiUiKit
2
- VERSION = "0.1.6"
2
+ VERSION = "0.1.7"
3
3
  end
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.7
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,21 @@ 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
1360
  - app/helpers/okonomi_ui_kit/breadcrumbs_helper.rb
1362
1361
  - app/helpers/okonomi_ui_kit/component.rb
1363
1362
  - app/helpers/okonomi_ui_kit/components/alert.rb
1364
1363
  - app/helpers/okonomi_ui_kit/components/badge.rb
1364
+ - app/helpers/okonomi_ui_kit/components/button_to.rb
1365
+ - app/helpers/okonomi_ui_kit/components/code.rb
1366
+ - app/helpers/okonomi_ui_kit/components/link_to.rb
1367
+ - app/helpers/okonomi_ui_kit/components/page.rb
1368
+ - app/helpers/okonomi_ui_kit/components/table.rb
1365
1369
  - app/helpers/okonomi_ui_kit/components/typography.rb
1370
+ - app/helpers/okonomi_ui_kit/config.rb
1366
1371
  - app/helpers/okonomi_ui_kit/form_builder.rb
1367
1372
  - app/helpers/okonomi_ui_kit/icon_helper.rb
1368
1373
  - app/helpers/okonomi_ui_kit/navigation_helper.rb
1369
- - app/helpers/okonomi_ui_kit/page_builder_helper.rb
1370
1374
  - app/helpers/okonomi_ui_kit/svg_icons.rb
1371
- - app/helpers/okonomi_ui_kit/table_helper.rb
1372
1375
  - app/helpers/okonomi_ui_kit/theme.rb
1373
1376
  - app/helpers/okonomi_ui_kit/theme_helper.rb
1374
1377
  - app/helpers/okonomi_ui_kit/ui_helper.rb
@@ -1386,6 +1389,9 @@ files:
1386
1389
  - app/views/layouts/okonomi_ui_kit/application.html.erb
1387
1390
  - app/views/okonomi/attribute_sections/_section.html.erb
1388
1391
  - app/views/okonomi/components/alert/_alert.html.erb
1392
+ - app/views/okonomi/components/code/_code.html.erb
1393
+ - app/views/okonomi/components/page/_page.html.erb
1394
+ - app/views/okonomi/components/table/_table.html.erb
1389
1395
  - app/views/okonomi/components/typography/_typography.html.erb
1390
1396
  - app/views/okonomi/forms/tailwind/_checkbox_label.html.erb
1391
1397
  - 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,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