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
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OkonomiUiKit
4
+ module Components
5
+ class Breadcrumbs < Component
6
+ register_styles do
7
+ {
8
+ base: "flex",
9
+ container: "isolate flex -space-x-px rounded-lg shadow-sm",
10
+ nav: "",
11
+ list: "flex items-center space-x-4",
12
+ item: {
13
+ base: "",
14
+ first: "",
15
+ last: "",
16
+ current: ""
17
+ },
18
+ link: {
19
+ base: "ml-4 text-sm font-medium text-gray-500 hover:text-gray-700",
20
+ first: "text-sm font-medium text-gray-500 hover:text-gray-700",
21
+ current: "ml-4 text-sm font-medium text-gray-500"
22
+ },
23
+ separator: {
24
+ base: "size-5 shrink-0 text-gray-400",
25
+ wrapper: ""
26
+ },
27
+ icon: "size-5 text-gray-400"
28
+ }
29
+ end
30
+
31
+ def initialize(template, options = {})
32
+ super
33
+ @items = []
34
+ @builder = BreadcrumbBuilder.new(self)
35
+ end
36
+
37
+ def render(options = {}, &block)
38
+ return "" if block.nil?
39
+
40
+ block.call(@builder)
41
+ view.render("okonomi/components/breadcrumbs/breadcrumbs",
42
+ component: self,
43
+ items: @items,
44
+ options: options
45
+ )
46
+ end
47
+
48
+ def add_item(text, path = nil, current: false, icon: nil)
49
+ @items << {
50
+ text: text,
51
+ path: path,
52
+ current: current,
53
+ icon: icon
54
+ }
55
+ end
56
+
57
+ class BreadcrumbBuilder
58
+ def initialize(component)
59
+ @component = component
60
+ end
61
+
62
+ def link(text, path = nil, current: false, icon: nil, **options)
63
+ # Ignore extra options for now
64
+ @component.add_item(text, path, current: current, icon: icon)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,56 @@
1
+ module OkonomiUiKit
2
+ module Components
3
+ class ButtonBase < OkonomiUiKit::Component
4
+ def build_button_class(variant:, color:, classes: '')
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(' ')
11
+ end
12
+
13
+ register_styles :default do
14
+ {
15
+ root: "hover:cursor-pointer text-sm",
16
+ outlined: {
17
+ 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",
18
+ 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"
26
+ }
27
+ },
28
+ contained: {
29
+ 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",
30
+ 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"
38
+ }
39
+ },
40
+ text: {
41
+ root: "text-base",
42
+ colors: {
43
+ default: "text-default-700 hover:underline",
44
+ primary: "text-primary-600 hover:underline",
45
+ secondary: "text-secondary-600 hover:underline",
46
+ success: "text-success-600 hover:underline",
47
+ danger: "text-danger-600 hover:underline",
48
+ warning: "text-warning-600 hover:underline",
49
+ info: "text-info-600 hover:underline"
50
+ }
51
+ }
52
+ }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,23 @@
1
+ module OkonomiUiKit
2
+ module Components
3
+ class ButtonTag < OkonomiUiKit::Components::ButtonBase
4
+ def render(name = nil, options = {}, &block)
5
+ options, name = options, block if block_given?
6
+
7
+ options ||= {}
8
+ options = options.with_indifferent_access
9
+
10
+ variant = (options.delete(:variant) || 'contained').to_sym
11
+ color = (options.delete(:color) || 'default').to_sym
12
+
13
+ options[:class] = build_button_class(variant: variant, color: color, classes: options[:class])
14
+
15
+ if block_given?
16
+ view.button_tag(options, &block)
17
+ else
18
+ view.button_tag(name, options)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module OkonomiUiKit
2
+ module Components
3
+ class ButtonTo < OkonomiUiKit::Components::ButtonBase
4
+ def render(name = nil, options = nil, html_options = nil, &block)
5
+ html_options, options, name = options, name, block if block_given?
6
+
7
+ html_options ||= {}
8
+ html_options = html_options.with_indifferent_access
9
+
10
+ variant = (html_options.delete(:variant) || 'contained').to_sym
11
+ color = (html_options.delete(:color) || 'default').to_sym
12
+
13
+ html_options[:class] = build_button_class(variant: variant, color: color, classes: html_options[:class])
14
+
15
+ if block_given?
16
+ view.button_to(options, html_options, &block)
17
+ else
18
+ view.button_to(name, options, html_options)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,73 @@
1
+ module OkonomiUiKit
2
+ module Components
3
+ class Code < OkonomiUiKit::Component
4
+ def render(content = nil, options = {}, &block)
5
+ options, content = content, nil if block_given?
6
+ options ||= {}
7
+ options = options.with_indifferent_access
8
+
9
+ # Extract component-specific options
10
+ language = options.delete(:language) || options.delete(:lang)
11
+ variant = (options.delete(:variant) || 'default').to_sym
12
+ size = (options.delete(:size) || 'default').to_sym
13
+ wrap = options.delete(:wrap) != false # Default to true
14
+
15
+ # Build classes
16
+ classes = build_classes(variant: variant, size: size, wrap: wrap, custom_class: options.delete(:class))
17
+
18
+ # Escape HTML entities in content
19
+ escaped_content = if block_given?
20
+ view.capture(&block)
21
+ elsif content
22
+ content
23
+ else
24
+ ""
25
+ end
26
+
27
+ view.render(
28
+ template_path,
29
+ content: escaped_content.strip.html_safe,
30
+ options: options,
31
+ classes: classes,
32
+ language: language
33
+ )
34
+ end
35
+
36
+ private
37
+
38
+ def build_classes(variant:, size:, wrap:, custom_class: nil)
39
+ base_classes = theme.dig(:components, :code, :base) || "bg-gray-900 text-gray-100 rounded-lg"
40
+
41
+ variant_classes = case variant
42
+ when :inline
43
+ "bg-gray-100 text-gray-900 px-1 py-0.5 rounded text-sm font-mono"
44
+ when :minimal
45
+ "bg-gray-900 text-gray-100 p-3 rounded text-xs"
46
+ else
47
+ # :default
48
+ "bg-gray-900 text-gray-100 p-4 rounded-lg"
49
+ end
50
+
51
+ size_classes = case size
52
+ when :xs
53
+ "text-xs"
54
+ when :sm
55
+ "text-sm"
56
+ when :lg
57
+ "text-base"
58
+ else
59
+ # :default
60
+ "text-sm"
61
+ end
62
+
63
+ wrap_classes = wrap ? "overflow-x-auto" : "overflow-hidden"
64
+
65
+ [base_classes, variant_classes, size_classes, wrap_classes, custom_class].compact.join(' ')
66
+ end
67
+
68
+ def html_escape(content)
69
+ ERB::Util.html_escape(content.to_s.strip)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,36 @@
1
+ module OkonomiUiKit
2
+ module Components
3
+ class Icon < OkonomiUiKit::Component
4
+ def render(name, options = {})
5
+ options = options.with_indifferent_access
6
+
7
+ # Extract specific icon options
8
+ variant = options.delete(:variant) || :outlined
9
+ width = options.delete(:width)
10
+ height = options.delete(:height)
11
+
12
+ # Build classes array
13
+ classes = [
14
+ style(:base),
15
+ options.delete(:class)
16
+ ].compact.join(' ')
17
+
18
+ view.render(
19
+ template_path,
20
+ name: name,
21
+ variant: variant,
22
+ width: width,
23
+ height: height,
24
+ classes: classes,
25
+ options: options
26
+ )
27
+ end
28
+
29
+ register_styles :default do
30
+ {
31
+ base: "inline-block"
32
+ }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,23 @@
1
+ module OkonomiUiKit
2
+ module Components
3
+ class LinkTo < OkonomiUiKit::Components::ButtonBase
4
+ def render(name = nil, options = nil, html_options = nil, &block)
5
+ html_options, options, name = options, name, block if block_given?
6
+
7
+ html_options ||= {}
8
+ html_options = html_options.with_indifferent_access
9
+
10
+ variant = (html_options.delete(:variant) || 'text').to_sym
11
+ color = (html_options.delete(:color) || 'default').to_sym
12
+
13
+ html_options[:class] = build_button_class(variant: variant, color: color, classes: html_options[:class])
14
+
15
+ if block_given?
16
+ view.link_to(options, html_options, &block)
17
+ else
18
+ view.link_to(name, options, html_options)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,247 @@
1
+ module OkonomiUiKit
2
+ module Components
3
+ class Page < OkonomiUiKit::Component
4
+ def render(options = {}, &block)
5
+ builder = PageBuilder.new(view)
6
+
7
+ view.render(template_path, builder: builder, options: options, &block)
8
+ end
9
+ end
10
+
11
+ class PageBuilder
12
+ include ActionView::Helpers::TagHelper
13
+ include ActionView::Helpers::CaptureHelper
14
+
15
+ def initialize(template)
16
+ @template = template
17
+ @content_parts = []
18
+ end
19
+
20
+ def page_header(**options, &block)
21
+ header_builder = PageHeaderBuilder.new(@template)
22
+ yield(header_builder) if block_given?
23
+ @content_parts << header_builder.render
24
+ nil
25
+ end
26
+
27
+ def section(**options, &block)
28
+ section_builder = SectionBuilder.new(@template)
29
+ section_builder.title(options[:title]) if options[:title]
30
+ yield(section_builder) if block_given?
31
+ @content_parts << section_builder.render
32
+ nil
33
+ end
34
+
35
+ def render_content
36
+ @template.safe_join(@content_parts)
37
+ end
38
+
39
+ def to_s
40
+ render_content
41
+ end
42
+
43
+ private
44
+
45
+ def tag
46
+ @template.tag
47
+ end
48
+
49
+ def capture(*args, &block)
50
+ @template.capture(*args, &block)
51
+ end
52
+ end
53
+
54
+ class PageHeaderBuilder
55
+ include ActionView::Helpers::TagHelper
56
+ include ActionView::Helpers::CaptureHelper
57
+
58
+ def initialize(template)
59
+ @template = template
60
+ @breadcrumbs_content = nil
61
+ @row_content = nil
62
+ end
63
+
64
+ def breadcrumbs(&block)
65
+ @breadcrumbs_content = @template.ui.breadcrumbs(&block)
66
+ end
67
+
68
+ def row(&block)
69
+ row_builder = PageHeaderRowBuilder.new(@template)
70
+ yield(row_builder) if block_given?
71
+ @row_content = row_builder.render
72
+ end
73
+
74
+ def render
75
+ content = []
76
+ content << @breadcrumbs_content if @breadcrumbs_content
77
+ content << @row_content if @row_content
78
+
79
+ tag.div(class: "flex flex-col gap-2") do
80
+ @template.safe_join(content.compact)
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def tag
87
+ @template.tag
88
+ end
89
+
90
+ def capture(*args, &block)
91
+ @template.capture(*args, &block)
92
+ end
93
+ end
94
+
95
+ class PageHeaderRowBuilder
96
+ include ActionView::Helpers::TagHelper
97
+ include ActionView::Helpers::CaptureHelper
98
+
99
+ def initialize(template)
100
+ @template = template
101
+ @title_content = nil
102
+ @actions_content = nil
103
+ end
104
+
105
+ def title(text, **options)
106
+ @title_content = tag.h1(text, class: "text-2xl font-bold leading-7 text-gray-900 truncate sm:text-3xl sm:tracking-tight")
107
+ end
108
+
109
+ def actions(&block)
110
+ @actions_content = tag.div(class: "mt-4 flex md:ml-4 md:mt-0 gap-2") do
111
+ capture(&block) if block_given?
112
+ end
113
+ end
114
+
115
+ def render
116
+ tag.div(class: "flex w-full justify-between items-center") do
117
+ content = []
118
+ content << @title_content if @title_content
119
+ content << @actions_content if @actions_content
120
+ @template.safe_join(content.compact)
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ def tag
127
+ @template.tag
128
+ end
129
+
130
+ def capture(*args, &block)
131
+ @template.capture(*args, &block)
132
+ end
133
+ end
134
+
135
+ class SectionBuilder
136
+ include ActionView::Helpers::TagHelper
137
+ include ActionView::Helpers::CaptureHelper
138
+
139
+ def initialize(template)
140
+ @template = template
141
+ @title_content = nil
142
+ @subtitle_content = nil
143
+ @actions_content = nil
144
+ @body_content = nil
145
+ @attributes = []
146
+ end
147
+
148
+ def title(text, **options)
149
+ @title_content = tag.h3(text, class: "text-base/7 font-semibold text-gray-900")
150
+ end
151
+
152
+ def subtitle(text, **options)
153
+ @subtitle_content = tag.p(text, class: "mt-1 max-w-2xl text-sm/6 text-gray-500")
154
+ end
155
+
156
+ def actions(&block)
157
+ @actions_content = tag.div(class: "mt-4 flex md:ml-4 md:mt-0") do
158
+ capture(&block) if block_given?
159
+ end
160
+ end
161
+
162
+ def body(&block)
163
+ if block_given?
164
+ # Capture the content first to see if attributes were used
165
+ content = capture { yield(self) }
166
+
167
+ @body_content = if @attributes.any?
168
+ # If attributes were added, wrap them in dl
169
+ tag.div do
170
+ tag.dl(class: "divide-y divide-gray-100") do
171
+ @template.safe_join(@attributes)
172
+ end
173
+ end
174
+ else
175
+ # Otherwise, just return the captured content
176
+ tag.div do
177
+ content
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ def attribute(label, value = nil, **options, &block)
184
+ content = if block_given?
185
+ capture(&block)
186
+ elsif value.respond_to?(:call)
187
+ value.call
188
+ else
189
+ value
190
+ end
191
+
192
+ attribute_html = tag.div(class: "py-6 sm:grid sm:grid-cols-3 sm:gap-4") do
193
+ dt_content = tag.dt(label, class: "text-sm font-medium text-gray-900")
194
+ dd_content = tag.dd(content, class: "mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0")
195
+
196
+ dt_content + dd_content
197
+ end
198
+
199
+ @attributes << attribute_html
200
+ end
201
+
202
+ def render
203
+ tag.div(class: "overflow-hidden bg-white") do
204
+ header_content = build_header
205
+ content_parts = []
206
+ content_parts << header_content if header_content.present?
207
+ content_parts << @body_content if @body_content
208
+ @template.safe_join(content_parts.compact)
209
+ end
210
+ end
211
+
212
+ private
213
+
214
+ def build_header
215
+ return nil unless @title_content || @subtitle_content || @actions_content
216
+
217
+ tag.div(class: "py-6") do
218
+ if @actions_content
219
+ tag.div(class: "flex w-full justify-between items-start") do
220
+ title_section = tag.div do
221
+ content_parts = []
222
+ content_parts << @title_content if @title_content
223
+ content_parts << @subtitle_content if @subtitle_content
224
+ @template.safe_join(content_parts.compact)
225
+ end
226
+
227
+ title_section + @actions_content
228
+ end
229
+ else
230
+ content_parts = []
231
+ content_parts << @title_content if @title_content
232
+ content_parts << @subtitle_content if @subtitle_content
233
+ @template.safe_join(content_parts.compact)
234
+ end
235
+ end
236
+ end
237
+
238
+ def tag
239
+ @template.tag
240
+ end
241
+
242
+ def capture(*args, &block)
243
+ @template.capture(*args, &block)
244
+ end
245
+ end
246
+ end
247
+ end