okonomi_ui_kit 0.1.5 → 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.
- checksums.yaml +4 -4
- data/app/assets/builds/okonomi_ui_kit/application.tailwind.css +313 -4
- data/app/helpers/okonomi_ui_kit/component.rb +80 -0
- data/app/helpers/okonomi_ui_kit/components/alert.rb +9 -0
- data/app/helpers/okonomi_ui_kit/components/badge.rb +31 -0
- data/app/helpers/okonomi_ui_kit/components/button_to.rb +34 -0
- data/app/helpers/okonomi_ui_kit/components/code.rb +73 -0
- data/app/helpers/okonomi_ui_kit/components/link_to.rb +34 -0
- data/app/helpers/okonomi_ui_kit/components/page.rb +247 -0
- data/app/helpers/okonomi_ui_kit/components/table.rb +207 -0
- data/app/helpers/okonomi_ui_kit/components/typography.rb +68 -0
- data/app/helpers/okonomi_ui_kit/config.rb +16 -0
- data/app/helpers/okonomi_ui_kit/theme.rb +55 -3
- data/app/helpers/okonomi_ui_kit/ui_helper.rb +35 -63
- data/app/javascript/okonomi_ui_kit/controllers/modal_controller.js +94 -0
- data/app/views/okonomi/components/alert/_alert.html.erb +3 -0
- data/app/views/okonomi/components/code/_code.html.erb +1 -0
- data/app/views/okonomi/components/page/_page.html.erb +5 -0
- data/app/views/okonomi/components/table/_table.html.erb +3 -0
- data/app/views/okonomi/components/typography/_typography.html.erb +7 -0
- data/app/views/okonomi/modals/_confirmation_modal.html.erb +77 -0
- data/lib/okonomi_ui_kit/engine.rb +0 -3
- data/lib/okonomi_ui_kit/version.rb +1 -1
- metadata +23 -7
- data/app/helpers/okonomi_ui_kit/badge_helper.rb +0 -23
- data/app/helpers/okonomi_ui_kit/page_builder_helper.rb +0 -217
- data/app/helpers/okonomi_ui_kit/table_helper.rb +0 -158
- data/app/views/okonomi/components/_typography.html.erb +0 -7
@@ -0,0 +1,80 @@
|
|
1
|
+
module OkonomiUiKit
|
2
|
+
class Component
|
3
|
+
attr_reader :view, :theme
|
4
|
+
|
5
|
+
def initialize(view, theme)
|
6
|
+
@view = view
|
7
|
+
@theme = theme || OkonomiUiKit::Theme::DEFAULT_THEME
|
8
|
+
end
|
9
|
+
|
10
|
+
def template_path
|
11
|
+
"okonomi/components/#{name}/#{name}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
self.class.name.demodulize.underscore
|
16
|
+
end
|
17
|
+
|
18
|
+
def style(*args)
|
19
|
+
styles.dig(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def styles
|
23
|
+
@combined_styles ||= combined_styles
|
24
|
+
end
|
25
|
+
|
26
|
+
def combined_styles
|
27
|
+
internal_name = internal_styles_registry.has_key?(theme_name) ? theme_name : :default
|
28
|
+
config_name = config_styles_registry.has_key?(theme_name) ? theme_name : :default
|
29
|
+
|
30
|
+
internal_styles = internal_styles_registry[internal_name] || {}
|
31
|
+
config_styles = config_styles_registry[config_name] || {}
|
32
|
+
|
33
|
+
{}.deep_merge(internal_styles).deep_merge(config_styles)
|
34
|
+
end
|
35
|
+
|
36
|
+
def internal_styles_registry
|
37
|
+
self.class.internal_styles_registry
|
38
|
+
end
|
39
|
+
|
40
|
+
def config_styles_registry
|
41
|
+
self.class.config_styles_registry
|
42
|
+
end
|
43
|
+
|
44
|
+
def theme_name
|
45
|
+
:default
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.config_styles_registry
|
49
|
+
return Hash.new({}) unless config_class?
|
50
|
+
|
51
|
+
config_class.styles_registry
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.config_class
|
55
|
+
return nil unless config_class?
|
56
|
+
|
57
|
+
Object.const_get(config_class_name)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.config_class_name
|
61
|
+
"OkonomiUiKit::Configs::#{name.demodulize}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.config_class?
|
65
|
+
Object.const_defined?(config_class_name)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.register_styles(theme = :default, &block)
|
69
|
+
styles = block.call if block_given?
|
70
|
+
|
71
|
+
raise ArgumentError, "Styles must be a Hash" unless styles.is_a?(Hash)
|
72
|
+
|
73
|
+
internal_styles_registry[theme] = styles if styles.is_a?(Hash)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.internal_styles_registry
|
77
|
+
@internal_styles_registry ||= {}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module OkonomiUiKit
|
2
|
+
module Components
|
3
|
+
class Badge < OkonomiUiKit::Component
|
4
|
+
def render(text, options = {})
|
5
|
+
options = options.with_indifferent_access
|
6
|
+
severity = (options.delete(:severity) || :default).to_sym
|
7
|
+
|
8
|
+
classes = [
|
9
|
+
style(:base),
|
10
|
+
style(:severities, severity) || '',
|
11
|
+
options.delete(:class) || ''
|
12
|
+
].reject(&:blank?).join(' ')
|
13
|
+
|
14
|
+
view.tag.span(text, class: classes, **options)
|
15
|
+
end
|
16
|
+
|
17
|
+
register_styles :default do
|
18
|
+
{
|
19
|
+
base: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium",
|
20
|
+
severities: {
|
21
|
+
default: "bg-gray-100 text-gray-800",
|
22
|
+
success: "bg-green-100 text-green-800",
|
23
|
+
danger: "bg-red-100 text-red-800",
|
24
|
+
info: "bg-blue-100 text-blue-800",
|
25
|
+
warning: "bg-yellow-100 text-yellow-800"
|
26
|
+
}
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module OkonomiUiKit
|
2
|
+
module Components
|
3
|
+
class ButtonTo < OkonomiUiKit::Component
|
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
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def build_button_class(variant:, color:, classes: '')
|
25
|
+
[
|
26
|
+
theme.dig(:components, :link, :root) || '',
|
27
|
+
theme.dig(:components, :link, variant.to_sym, :root) || '',
|
28
|
+
theme.dig(:components, :link, variant.to_sym, :colors, color.to_sym) || '',
|
29
|
+
classes,
|
30
|
+
].reject(&:blank?).join(' ')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
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,34 @@
|
|
1
|
+
module OkonomiUiKit
|
2
|
+
module Components
|
3
|
+
class LinkTo < OkonomiUiKit::Component
|
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
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def build_button_class(variant:, color:, classes: '')
|
25
|
+
[
|
26
|
+
theme.dig(:components, :link, :root) || '',
|
27
|
+
theme.dig(:components, :link, variant.to_sym, :root) || '',
|
28
|
+
theme.dig(:components, :link, variant.to_sym, :colors, color.to_sym) || '',
|
29
|
+
classes,
|
30
|
+
].reject(&:blank?).join(' ')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
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.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
|