lesli_view 1.1.2 → 1.1.3

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/lesli_view/charts/bar.rb +10 -0
  3. data/lib/lesli_view/charts/general.html.erb +38 -52
  4. data/lib/lesli_view/charts/general.rb +69 -30
  5. data/lib/lesli_view/charts/line.rb +16 -0
  6. data/lib/lesli_view/components/header.html.erb +48 -23
  7. data/lib/lesli_view/components/header.rb +35 -2
  8. data/lib/lesli_view/components/toolbar.css +52 -0
  9. data/lib/lesli_view/components/toolbar.html.erb +36 -21
  10. data/lib/lesli_view/components/toolbar.rb +41 -4
  11. data/lib/lesli_view/elements/button.css +357 -0
  12. data/lib/lesli_view/elements/button.html.erb +15 -12
  13. data/lib/lesli_view/elements/button.rb +74 -34
  14. data/lib/lesli_view/elements/empty.html.erb +31 -6
  15. data/lib/lesli_view/elements/empty.rb +8 -1
  16. data/lib/lesli_view/elements/table-chart.html.erb +42 -0
  17. data/lib/lesli_view/elements/table.css +91 -0
  18. data/lib/lesli_view/elements/table.html.erb +24 -21
  19. data/lib/lesli_view/elements/table.rb +67 -17
  20. data/lib/lesli_view/forms/builder.rb +16 -3
  21. data/lib/lesli_view/forms/builder_horizontal.rb +34 -10
  22. data/lib/lesli_view/forms/fields.rb +103 -70
  23. data/lib/lesli_view/forms/fieldset.rb +3 -3
  24. data/lib/lesli_view/forms/form.css +171 -0
  25. data/lib/lesli_view/forms/form.scss +2 -54
  26. data/lib/lesli_view/forms/inputs.rb +27 -15
  27. data/lib/lesli_view/layout/container.html.erb +8 -2
  28. data/lib/lesli_view/version.rb +2 -2
  29. data/lib/lesli_view/widgets/calendar.html.erb +42 -133
  30. data/lib/lesli_view/widgets/calendar.rb +77 -3
  31. data/lib/lesli_view/widgets/chart.html.erb +25 -26
  32. data/lib/lesli_view/widgets/chart.rb +46 -3
  33. data/lib/lesli_view/widgets/count.html.erb +17 -15
  34. data/lib/lesli_view/widgets/count.rb +13 -1
  35. data/lib/lesli_view/widgets/date.html.erb +23 -21
  36. data/lib/lesli_view/widgets/date.rb +27 -2
  37. data/lib/lesli_view/widgets/weather.html.erb +40 -38
  38. data/lib/lesli_view/widgets/weather.rb +41 -2
  39. data/readme.md +158 -29
  40. metadata +6 -4
  41. data/lib/lesli_view/components/header.scss +0 -69
  42. data/lib/lesli_view/components/toolbar.scss +0 -54
  43. data/lib/lesli_view/elements/table.scss +0 -153
@@ -0,0 +1,91 @@
1
+ /* Tailwind styles for LesliView::Elements::Table. */
2
+
3
+ @layer components {
4
+ .lesli-table-container {
5
+ @apply w-full overflow-x-auto rounded-2xl border border-slate-200 bg-white shadow-sm;
6
+ }
7
+
8
+ .lesli-table {
9
+ width: 100%;
10
+ min-width: 40rem;
11
+ border-collapse: collapse;
12
+ @apply text-left text-sm text-slate-600;
13
+ }
14
+
15
+ .lesli-table thead {
16
+ @apply border-b border-slate-200 bg-slate-50;
17
+ }
18
+
19
+ .lesli-table th {
20
+ @apply whitespace-nowrap px-4 py-3 text-xs font-semibold uppercase tracking-wide text-slate-600;
21
+ }
22
+
23
+ .lesli-table td {
24
+ @apply border-b border-slate-100 px-4 py-3 align-middle;
25
+ }
26
+
27
+ .lesli-table tbody tr {
28
+ @apply transition-colors hover:bg-sky-50/50;
29
+ }
30
+
31
+ .lesli-table tbody tr:last-child td {
32
+ @apply border-b-0;
33
+ }
34
+
35
+ .lesli-table--striped tbody tr:nth-child(even) {
36
+ @apply bg-slate-50/60;
37
+ }
38
+
39
+ .lesli-table--striped tbody tr:nth-child(even):hover {
40
+ @apply bg-sky-50/70;
41
+ }
42
+
43
+ .lesli-table--compact th,
44
+ .lesli-table--compact td {
45
+ @apply px-3 py-2;
46
+ }
47
+
48
+ .lesli-table__cell--center {
49
+ @apply text-center;
50
+ }
51
+
52
+ .lesli-table__cell--right {
53
+ @apply text-right;
54
+ }
55
+
56
+ .lesli-table__cell--left {
57
+ @apply text-left;
58
+ }
59
+
60
+ .lesli-table td.lesli-table__cell--linked {
61
+ @apply p-0;
62
+ }
63
+
64
+ .lesli-table__link {
65
+ @apply block px-4 py-3 text-slate-700 transition-colors hover:text-sky-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-sky-500/40;
66
+ }
67
+
68
+ .lesli-table--compact .lesli-table__link {
69
+ @apply px-3 py-2;
70
+ }
71
+
72
+ .lesli-table__row--danger {
73
+ @apply bg-rose-50 text-rose-900;
74
+ }
75
+
76
+ .lesli-table__row--warning {
77
+ @apply bg-amber-50 text-amber-900;
78
+ }
79
+
80
+ .lesli-table__row--info {
81
+ @apply bg-sky-50 text-sky-900;
82
+ }
83
+
84
+ .lesli-table__row--success {
85
+ @apply bg-emerald-50 text-emerald-900;
86
+ }
87
+
88
+ .lesli-table__empty {
89
+ @apply py-12 text-center text-sm text-slate-400;
90
+ }
91
+ }
@@ -1,24 +1,18 @@
1
- <div class="lesli-table-container table-container">
2
- <table id="<%= id %>" class="table is-fullwidth lesli-table <%= class_name %>">
1
+ <div class="lesli-table-container">
2
+ <table id="<%= id %>" class="<%= table_classes %>">
3
+ <% if caption.present? %>
4
+ <caption class="sr-only"><%= caption %></caption>
5
+ <% end %>
6
+
3
7
  <% unless headless %>
4
8
  <thead>
5
9
  <tr>
6
10
  <% columns.each do |column| %>
7
- <th
8
- width="<%= column[:width] %>"
9
- class="<%= table_header_class(column) %>"
10
- data-action="click->table#sort"
11
- data-field="<%= column[:field] %>">
12
- <% if column[:sort] %>
13
- <span class="icon-text">
14
- <span><%= column[:label] %></span>
15
- <span class="icon">
16
- <span class="material-symbols"><%= column[:field] == @current_sort ? (current_sort_dir(column) == "asc" ? "arrow_upward" : "arrow_downward") : "sort" %></span>
17
- </span>
18
- </span>
19
- <% else %>
20
- <%= column[:label] %>
21
- <% end %>
11
+ <th
12
+ scope="col"
13
+ width="<%= column[:width] %>"
14
+ class="<%= column_class(column) %>">
15
+ <%= column[:label] %>
22
16
  </th>
23
17
  <% end %>
24
18
  </tr>
@@ -26,14 +20,15 @@
26
20
  <% end %>
27
21
  <tbody>
28
22
  <% if records %>
29
- <% records.each_with_index do |record, i| %>
23
+ <% records.each do |record| %>
30
24
  <tr>
31
25
  <% columns.each do |column| %>
32
- <td class="<%= table_body_class(column) %>">
26
+ <% value = value_for(record, column) %>
27
+ <td class="<%= class_names(column_class(column), "lesli-table__cell--linked": link.present?) %>">
33
28
  <% if link %>
34
- <%= link_to(record[column[:field]] || "", link.call(record), class: "link", data: { turbo_frame: '_top' }) %>
29
+ <%= link_to(value || "", link.call(record), class: "lesli-table__link", data: { turbo_frame: "_top" }) %>
35
30
  <% else %>
36
- <%= record[column[:field]] %>
31
+ <%= value %>
37
32
  <% end %>
38
33
  </td>
39
34
  <% end %>
@@ -43,6 +38,14 @@
43
38
  <% rows.each do |row| %>
44
39
  <%= row %>
45
40
  <% end %>
41
+
42
+ <% if empty? %>
43
+ <tr>
44
+ <td class="lesli-table__empty" colspan="<%= [columns.size, 1].max %>">
45
+ <%= empty_text %>
46
+ </td>
47
+ </tr>
48
+ <% end %>
46
49
  </tbody>
47
50
  </table>
48
51
  </div>
@@ -33,50 +33,100 @@ Building a better future, one line of code at a time.
33
33
  module LesliView
34
34
  module Elements
35
35
  class Table < ViewComponent::Base
36
- attr_reader :id, :class_name, :pagination, :loading, :headless, :columns, :records, :link
36
+ attr_reader :id, :class_name, :headless, :columns, :records, :link, :caption, :empty_text
37
37
 
38
38
  renders_many :rows, "TableRow"
39
39
 
40
- def initialize(columns: nil, records: nil, id: nil, class_name: "is-striped", pagination: nil, loading: false, headless: false, link: nil)
40
+ def initialize(
41
+ columns: nil,
42
+ records: nil,
43
+ id: nil,
44
+ class_name: nil,
45
+ headless: false,
46
+ link: nil,
47
+ caption: nil,
48
+ empty: nil,
49
+ striped: true,
50
+ compact: false
51
+ )
41
52
  @id = id
42
53
  @class_name = class_name
43
- @pagination = pagination
44
- @loading = loading
45
54
  @headless = headless
46
- @columns = columns
55
+ @columns = Array(columns).map { |column| column.to_h.symbolize_keys }
47
56
  @records = records
48
57
  @link = link
58
+ @caption = caption
59
+ @empty_text = empty || I18n.t("lesli_view.table.empty", default: "No records found")
60
+ @striped = striped
61
+ @compact = compact
49
62
  end
50
63
 
51
- def table_header_class(column)
52
- column[:field] == "id" || column[:align] == "center" ? "has-text-centered" : ""
64
+ def table_classes
65
+ class_names(
66
+ "lesli-table",
67
+ ("lesli-table--striped" if @striped),
68
+ ("lesli-table--compact" if @compact),
69
+ class_name
70
+ )
53
71
  end
54
72
 
55
- def table_body_class(column)
56
- column[:field] == "id" || column[:align] == "center" ? "has-text-centered" : ""
73
+ def column_class(column)
74
+ alignment = column[:align]&.to_sym
75
+ alignment = :center if column[:field].to_s == "id"
76
+ return unless %i[left center right].include?(alignment)
77
+
78
+ "lesli-table__cell--#{alignment}"
79
+ end
80
+
81
+ def value_for(record, column)
82
+ field = column.fetch(:field)
83
+ field_name = field.to_s
84
+
85
+ if record.respond_to?(:attributes) && record.attributes.key?(field_name)
86
+ record.attributes[field_name]
87
+ elsif record.respond_to?(:key?)
88
+ return record[field] if record.key?(field)
89
+ return record[field_name] if record.key?(field_name)
90
+ return record[field_name.to_sym] if record.key?(field_name.to_sym)
91
+ elsif record.respond_to?(field_name)
92
+ record.public_send(field_name)
93
+ end
94
+ end
95
+
96
+ def empty?
97
+ records.present? ? records.empty? : rows.empty?
57
98
  end
58
99
 
59
100
  class TableRow < ViewComponent::Base
60
101
  renders_many :cells, "TableData"
61
102
 
62
- def initialize(css_class: "")
63
- @css_class = css_class
103
+ STATUSES = %w[danger info success warning].freeze
104
+
105
+ def initialize(class_name: nil, status: nil)
106
+ @class_name = class_name
107
+ @status = status.to_s if STATUSES.include?(status.to_s)
64
108
  end
65
109
 
66
110
  def call
67
- # safe_joins ensure multiple <td> elements render correctly
68
- content_tag(:tr, safe_join(cells), class:@css_class)
111
+ content_tag(
112
+ :tr,
113
+ safe_join(cells),
114
+ class: class_names(@class_name, @status && "lesli-table__row--#{@status}")
115
+ )
69
116
  end
70
117
 
71
118
  class TableData < ViewComponent::Base
72
- attr_reader :css_class
119
+ attr_reader :class_name
73
120
 
74
- def initialize(css_class: "")
75
- @css_class = css_class
121
+ def initialize(class_name: nil, align: nil)
122
+ @class_name = class_names(
123
+ class_name,
124
+ ("lesli-table__cell--#{align}" if %i[left center right].include?(align&.to_sym))
125
+ )
76
126
  end
77
127
 
78
128
  def call
79
- content_tag :td, content, class: css_class
129
+ content_tag(:td, content, class: class_name)
80
130
  end
81
131
  end
82
132
  end
@@ -35,9 +35,22 @@ Building a better future, one line of code at a time.
35
35
  module LesliView
36
36
  module Forms
37
37
  class Builder < ActionView::Helpers::FormBuilder
38
- def css_category category=nil
39
- return "is-#{category}"
40
- end
38
+ CATEGORY_ALIASES = {
39
+ alert: :danger,
40
+ error: :danger,
41
+ notice: :info
42
+ }.freeze
43
+
44
+ def css_category(category = nil)
45
+ return if category.blank?
46
+
47
+ normalized_category = CATEGORY_ALIASES.fetch(category.to_sym, category.to_sym)
48
+ "is-#{normalized_category}"
49
+ end
50
+
51
+ def merge_css_classes(*classes)
52
+ classes.flatten.compact.flat_map { |value| value.to_s.split }.reject(&:blank?).uniq.join(" ")
53
+ end
41
54
 
42
55
  include LesliView::Forms::Fields
43
56
  include LesliView::Forms::Inputs
@@ -1,25 +1,49 @@
1
1
  module LesliView
2
2
  module Forms
3
3
  class BuilderHorizontal < Builder
4
- def field_control(attribute, label: nil, message:nil, category:nil, icon:nil)
5
- super(attribute, label:label, message:message, category:category, icon:icon, horizontal:true)
4
+ def field_control(attribute, label: nil, message: nil, category: nil, icon: nil, **options)
5
+ super(attribute, label: label, message: message, category: category, icon: icon, horizontal: true, **options)
6
6
  end
7
7
 
8
- def field_select(attribute, choices, options = {}, html_options = {}, label: nil, humanize: true)
9
- super(attribute, choices, options, html_options, label: label, humanize: humanize, horizontal: true)
8
+ def field_control_text(attribute, label: nil, message: nil, category: nil, icon: nil, **options)
9
+ super(attribute, label: label, message: message, category: category, icon: icon, horizontal: true, **options)
10
+ end
11
+
12
+ def field_control_email(attribute, label: nil, message: nil, category: nil, icon: nil, **options)
13
+ super(attribute, label: label, message: message, category: category, icon: icon, horizontal: true, **options)
14
+ end
15
+
16
+ def field_control_checkbox(attribute, label: nil, message: nil, category: nil, icon: nil, **options)
17
+ super(attribute, label: label, message: message, category: category, icon: icon, horizontal: true, **options)
18
+ end
19
+
20
+ def field_control_password(attribute, label: nil, message: nil, category: nil, icon: nil, **options)
21
+ super(attribute, label: label, message: message, category: category, icon: icon, horizontal: true, **options)
22
+ end
23
+
24
+ def field_control_textarea(attribute, label: nil, message: nil, category: nil, icon: nil, **options)
25
+ super(attribute, label: label, message: message, category: category, icon: icon, horizontal: true, **options)
26
+ end
27
+
28
+ def field_textarea(attribute, **options)
29
+ field_control_textarea(attribute, **options)
30
+ end
31
+
32
+ def field_select(attribute, choices, options = {}, html_options = {}, label: nil, humanize: true, **field_options)
33
+ field_control_select(attribute, choices, label: label, humanize: humanize, options: options, html_options: html_options, **field_options)
10
34
  end
11
35
 
12
36
  def field_control_submit(value = nil, options = {})
13
37
  super(value, options, horizontal:true)
14
38
  end
15
39
 
16
- def field_control_button(value = nil, options = {}, icon:nil, horizontal: false)
17
- super(value, options, icon:icon, horizontal:true)
18
- end
40
+ def field_control_button(value = nil, options = {}, category: "primary", icon: nil, type: "submit")
41
+ super(value, options, category: category, icon: icon, type: type, horizontal: true)
42
+ end
19
43
 
20
- def field_control_select(attribute, choices, label: nil, message:nil, category:nil, icon:nil, humanize:true)
21
- super(attribute, choices, label:label, message:message, category:category, icon:icon, horizontal:true, humanize:humanize)
22
- end
44
+ def field_control_select(attribute, choices, label: nil, message: nil, category: nil, icon: nil, humanize: true, options: {}, html_options: {})
45
+ super(attribute, choices, label: label, message: message, category: category, icon: icon, horizontal: true, humanize: humanize, options: options, html_options: html_options)
46
+ end
23
47
  end
24
48
  end
25
49
  end
@@ -1,67 +1,113 @@
1
1
  module LesliView
2
2
  module Forms
3
3
  module Fields
4
- def field_control(attribute, label: nil, message:nil, category:nil, icon:nil, horizontal: false)
5
- field_control_text(attribute, label:label, message:message, category:category, icon:icon, horizontal:horizontal)
4
+ def field_control(attribute, label: nil, message: nil, category: nil, icon: nil, horizontal: false, **options)
5
+ field_control_text(attribute, label: label, message: message, category: category, icon: icon, horizontal: horizontal, **options)
6
6
  end
7
7
 
8
- def field_control_checkbox(attribute, label: nil, message:nil, category:nil, icon:nil, horizontal: false)
8
+ def field_control_checkbox(attribute, label: nil, message: nil, category: nil, icon: nil, horizontal: false, **options)
9
+ label_text = label.presence || attribute.to_s.humanize
10
+ checkbox_html = check_box(attribute, options, true, false)
11
+ control_html = @template.content_tag(:label, class: "lesli-form-checkbox-label") do
12
+ @template.safe_join([
13
+ checkbox_html,
14
+ @template.content_tag(:span, label_text)
15
+ ])
16
+ end
17
+
18
+ field_control_builder(
19
+ control_html: control_html,
20
+ message_text: message,
21
+ category: category,
22
+ horizontal: horizontal
23
+ )
24
+ end
25
+
26
+ def field_control_password(attribute, label: nil, message: nil, category: nil, icon: nil, horizontal: false, **options)
9
27
  label_html = label(attribute, label)
10
- text_field_html = check_box(attribute, {}, true, false)
28
+ control_html = password_field(attribute, options)
11
29
 
12
30
  field_control_builder(
13
31
  label_html: label_html,
14
- control_html: text_field_html,
32
+ control_html: control_html,
33
+ message_text: message,
34
+ category: category,
35
+ icon: icon,
15
36
  horizontal: horizontal
16
37
  )
17
38
  end
18
39
 
19
- def field_control_text(attribute, label: nil, message:nil, category:nil, icon:nil, horizontal: false)
40
+ def field_control_text(attribute, label: nil, message: nil, category: nil, icon: nil, horizontal: false, **options)
20
41
  label_html = label(attribute, label)
21
- text_field_html = text_field(attribute)
42
+ control_html = text_field(attribute, options)
22
43
 
23
44
  field_control_builder(
24
45
  label_html: label_html,
25
- control_html: text_field_html,
46
+ control_html: control_html,
47
+ message_text: message,
48
+ category: category,
49
+ icon: icon,
26
50
  horizontal: horizontal
27
51
  )
28
52
  end
29
53
 
30
- def field_control_email(attribute, label: nil, message:nil, category:nil, icon:nil, horizontal: false)
54
+ def field_control_email(attribute, label: nil, message: nil, category: nil, icon: nil, horizontal: false, **options)
31
55
  label_html = label(attribute, label)
32
- control_html = email_field(attribute)
56
+ control_html = email_field(attribute, options)
33
57
 
34
58
  field_control_builder(
35
59
  label_html: label_html,
36
60
  control_html: control_html,
61
+ message_text: message,
62
+ category: category,
37
63
  horizontal: horizontal,
38
- icon:icon
64
+ icon: icon
39
65
  )
40
66
  end
41
67
 
42
- def field_control_select(attribute, choices, label: nil, message:nil, category:nil, icon:nil, horizontal: false, humanize:true)
43
- choices = choices.map { |k, v| [k.humanize.capitalize, v] } if humanize
44
- value = @object.send(attribute)
68
+ def field_control_select(attribute, choices, label: nil, message: nil, category: nil, icon: nil, horizontal: false, humanize: true, options: {}, html_options: {})
69
+ choices = choices.map { |text, value| [text.to_s.humanize, value] } if humanize
45
70
  label_html = label(attribute, label)
46
- select_html = select(attribute, choices, {}, { value: value })
71
+ select_html = select(attribute, choices, options, html_options)
47
72
 
48
73
  field_control_builder(
49
74
  label_html: label_html,
50
- control_html: @template.content_tag(:div, select_html, class: "select is-fullwidth"),
75
+ control_html: @template.content_tag(:div, select_html, class: "lesli-form-select-wrapper"),
76
+ message_text: message,
77
+ category: category,
78
+ icon: icon,
51
79
  horizontal: horizontal
52
80
  )
53
81
  end
54
82
 
55
- def field_control_textarea(attribute, label:nil, message:nil, category:nil, icon:nil, horizontal: false)
56
-
57
- label_html = label == false ? '' : label(attribute, label)
58
- control_html = rich_textarea(attribute)
83
+ def field_control_textarea(attribute, label: nil, message: nil, category: nil, icon: nil, horizontal: false, **options)
84
+ label_html = label == false ? nil : label(attribute, label)
85
+ control_html = rich_textarea(attribute, options)
59
86
 
60
87
  field_control_builder(
61
88
  label_html: label_html,
62
89
  control_html: control_html,
90
+ message_text: message,
91
+ category: category,
63
92
  horizontal: horizontal,
64
- icon:icon
93
+ icon: icon
94
+ )
95
+ end
96
+
97
+ # Backward-compatible shorthand used by older Lesli forms.
98
+ def field_textarea(attribute, **options)
99
+ field_control_textarea(attribute, **options)
100
+ end
101
+
102
+ def field_select(attribute, choices, options = {}, html_options = {}, label: nil, humanize: true, **field_options)
103
+ field_control_select(
104
+ attribute,
105
+ choices,
106
+ label: label,
107
+ humanize: humanize,
108
+ options: options,
109
+ html_options: html_options,
110
+ **field_options
65
111
  )
66
112
  end
67
113
 
@@ -70,75 +116,62 @@ module LesliView
70
116
  field_control_builder(control_html: submit_html, horizontal: horizontal)
71
117
  end
72
118
 
73
- def field_control_button(value = nil, options = {}, category:"primary", icon:nil, type:"submit", horizontal: false)
74
- # Merge user-provided classes with default button classes
75
- button_classes = ["button", "is-#{category}"]
119
+ def field_control_button(value = nil, options = {}, category: "primary", icon: nil, type: "submit", horizontal: false)
120
+ options = options.symbolize_keys
121
+ button_classes = merge_css_classes("lesli-form-button", css_category(category), options.delete(:class))
76
122
 
77
- button_html = @template.content_tag(:button, class: button_classes, type: type, **options.except(:class)) do
78
- html = "".html_safe
123
+ button_html = @template.content_tag(:button, class: button_classes, type: type, **options) do
124
+ content = []
79
125
  if icon.present?
80
- html << @template.content_tag(:span, class: "icon") do
126
+ content << @template.content_tag(:span, class: "lesli-form-button-icon", aria: { hidden: true }) do
81
127
  @template.content_tag(:span, icon, class: "material-symbols")
82
128
  end
83
129
  end
84
- html << @template.content_tag(:span, value) if value.present?
85
- html
130
+ content << @template.content_tag(:span, value) if value.present?
131
+ @template.safe_join(content)
86
132
  end
87
- field_control_builder(control_html: button_html, icon:nil, horizontal: horizontal)
133
+ field_control_builder(control_html: button_html, horizontal: horizontal)
88
134
  end
89
135
 
90
136
  def field_control_builder(
91
- label_html:nil,
92
- control_html:nil,
93
- message_text:nil,
94
- horizontal:false,
95
- category:nil,
96
- icon:nil,
137
+ label_html: nil,
138
+ control_html: nil,
139
+ message_text: nil,
140
+ horizontal: false,
141
+ category: nil,
142
+ icon: nil,
97
143
  &block
98
144
  )
99
-
100
- icon_left = icon
101
- icon_right = nil
102
-
103
- # Conditionally add 'is-horizontal' if horizontal is true
104
- field_classes = ['field']
105
- field_classes << 'is-horizontal' if horizontal
106
-
107
- control_classes = ['control']
108
- control_classes << 'has-icons-left' if icon_left
109
- control_classes << 'has-icons-right' if icon_right
110
-
111
- help_class = ""
112
-
113
- @template.content_tag(:div, class: 'lesli-form-field mb-3') do
114
- @template.content_tag(:div, class: field_classes.join(' ')) do
115
- @template.content_tag(:div, label_html, class: 'field-label is-normal mb-1') +
116
- @template.content_tag(:div, class: 'field-body') do
117
- @template.content_tag(:div, class: 'field') do
118
- control_html = @template.content_tag(:div, class: control_classes.join(' ')) do
119
- content = ''.html_safe
120
- content << (block_given? ? @template.capture(&block) : control_html)
121
- content << icon_html(icon_left, 'left') if icon_left
122
- content << icon_html(icon_right, 'right') if icon_right
123
- content
124
- end
125
- control_html + @template.content_tag(:p, message_text, class: "help #{css_category(category)}")
126
- end
145
+ field_classes = merge_css_classes("lesli-form-field", horizontal && "is-horizontal", css_category(category))
146
+ control_classes = merge_css_classes("lesli-form-control", icon.present? && "has-icon")
147
+ captured_control = block_given? ? @template.capture(&block) : control_html
148
+
149
+ @template.content_tag(:div, class: field_classes) do
150
+ content = []
151
+ content << @template.content_tag(:div, label_html, class: "lesli-form-label-wrapper") if label_html.present?
152
+ content << @template.content_tag(:div, class: "lesli-form-field-body") do
153
+ body = []
154
+ body << @template.content_tag(:div, class: control_classes) do
155
+ @template.safe_join([captured_control, icon_html(icon)].compact)
127
156
  end
157
+ if message_text.present?
158
+ body << @template.content_tag(:p, message_text, class: merge_css_classes("lesli-form-help", css_category(category)))
159
+ end
160
+ @template.safe_join(body)
128
161
  end
162
+ @template.safe_join(content)
129
163
  end
130
164
  end
131
165
 
132
166
  private
133
167
 
134
- def icon_html(icon_class, position)
135
- return ''.html_safe unless icon_class
168
+ def icon_html(icon_name)
169
+ return unless icon_name.present?
136
170
 
137
- @template.content_tag(:span, class: "icon is-small is-#{position}") do
138
- #@template.content_tag(:i, '', class: icon_class)
139
- @template.content_tag(:span, icon_class, class: "material-symbols-outlined")
171
+ @template.content_tag(:span, class: "lesli-form-icon", aria: { hidden: true }) do
172
+ @template.content_tag(:span, icon_name, class: "material-symbols-outlined")
140
173
  end
141
- end
174
+ end
142
175
  end
143
176
  end
144
177
  end
@@ -2,11 +2,11 @@ module LesliView
2
2
  module Forms
3
3
  module Fieldset
4
4
  def fieldset(legend = "", options = {}, category: nil, &block)
5
- options[:class] = ["lesli-form-fieldset", "box", "pr-6", "is-#{category}"]
6
- options[:class].push("pt-5") unless legend.present?
5
+ options = options.symbolize_keys
6
+ options[:class] = merge_css_classes("lesli-form-fieldset", css_category(category), options[:class])
7
7
 
8
8
  @template.content_tag(:fieldset, options) do
9
- legend_html = legend.present? ? @template.content_tag(:h5, legend, class: ["is-size-5", "mb-5", "ml-2"]) : nil
9
+ legend_html = legend.present? ? @template.content_tag(:legend, legend, class: "lesli-form-legend") : nil
10
10
  @template.safe_join([legend_html, @template.capture(&block)].compact)
11
11
  end
12
12
  end