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,171 @@
1
+ /* Tailwind styles for LesliView::Forms builders. */
2
+
3
+ @layer components {
4
+ .lesli-form-field {
5
+ @apply mb-4;
6
+ }
7
+
8
+ .lesli-form-label-wrapper {
9
+ @apply mb-2;
10
+ }
11
+
12
+ .lesli-form-label {
13
+ @apply block text-sm font-medium leading-5 text-slate-700;
14
+ }
15
+
16
+ .lesli-form-field-body {
17
+ @apply min-w-0;
18
+ }
19
+
20
+ .lesli-form-control {
21
+ @apply relative;
22
+ }
23
+
24
+ .lesli-form-input,
25
+ .lesli-form-select,
26
+ .lesli-form-control textarea,
27
+ .lesli-form-control trix-editor {
28
+ @apply block w-full rounded-xl border border-slate-300 bg-white px-3.5 py-2.5 text-sm text-slate-900 shadow-sm outline-none transition placeholder:text-slate-400 hover:border-slate-400 focus:border-sky-500 focus:ring-4 focus:ring-sky-500/10 disabled:cursor-not-allowed disabled:bg-slate-100 disabled:text-slate-500 disabled:opacity-75;
29
+ }
30
+
31
+ .lesli-form-input,
32
+ .lesli-form-select {
33
+ @apply h-11;
34
+ }
35
+
36
+ .lesli-form-control textarea,
37
+ .lesli-form-control trix-editor {
38
+ @apply min-h-32 resize-y;
39
+ }
40
+
41
+ .lesli-form-select-wrapper {
42
+ @apply relative;
43
+ }
44
+
45
+ .lesli-form-select {
46
+ @apply cursor-pointer appearance-none pr-10;
47
+ }
48
+
49
+ .lesli-form-select-wrapper::after {
50
+ @apply pointer-events-none absolute right-3.5 top-1/2 h-2 w-2 -translate-y-2 rotate-45 border-b-2 border-r-2 border-slate-400 content-[''];
51
+ }
52
+
53
+ .lesli-form-checkbox {
54
+ @apply h-4 w-4 cursor-pointer rounded border-slate-300 accent-sky-600 focus:ring-2 focus:ring-sky-500/30 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50;
55
+ }
56
+
57
+ .lesli-form-checkbox-label {
58
+ @apply inline-flex cursor-pointer items-center gap-2.5 text-sm text-slate-600;
59
+ }
60
+
61
+ .lesli-form-control.has-icon .lesli-form-input,
62
+ .lesli-form-control.has-icon .lesli-form-select {
63
+ @apply pl-11;
64
+ }
65
+
66
+ .lesli-form-icon {
67
+ @apply pointer-events-none absolute left-3.5 top-1/2 inline-flex h-5 w-5 -translate-y-1/2 items-center justify-center text-slate-400;
68
+ }
69
+
70
+ .lesli-form-icon .material-symbols-outlined {
71
+ @apply text-xl leading-none;
72
+ }
73
+
74
+ .lesli-form-help {
75
+ @apply mt-2 text-sm leading-5 text-slate-500;
76
+ }
77
+
78
+ .lesli-form-field.is-danger .lesli-form-input,
79
+ .lesli-form-field.is-danger .lesli-form-select,
80
+ .lesli-form-field.is-danger textarea,
81
+ .lesli-form-field.is-danger trix-editor {
82
+ @apply border-rose-400 focus:border-rose-500 focus:ring-rose-500/10;
83
+ }
84
+
85
+ .lesli-form-help.is-danger {
86
+ @apply text-rose-700;
87
+ }
88
+
89
+ .lesli-form-help.is-warning {
90
+ @apply text-amber-700;
91
+ }
92
+
93
+ .lesli-form-help.is-success {
94
+ @apply text-emerald-700;
95
+ }
96
+
97
+ .lesli-form-help.is-info {
98
+ @apply text-sky-700;
99
+ }
100
+
101
+ .lesli-form-button {
102
+ @apply inline-flex h-11 cursor-pointer items-center justify-center gap-2 rounded-xl border border-transparent px-4 text-sm font-semibold shadow-sm transition focus:outline-none focus:ring-4 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50;
103
+ }
104
+
105
+ .lesli-form-button.is-primary,
106
+ .lesli-form-button.is-info {
107
+ @apply bg-sky-600 text-white hover:bg-sky-700 focus:ring-sky-500/20;
108
+ }
109
+
110
+ .lesli-form-button.is-success {
111
+ @apply bg-emerald-600 text-white hover:bg-emerald-700 focus:ring-emerald-500/20;
112
+ }
113
+
114
+ .lesli-form-button.is-warning {
115
+ @apply bg-amber-500 text-amber-950 hover:bg-amber-600 focus:ring-amber-500/20;
116
+ }
117
+
118
+ .lesli-form-button.is-danger {
119
+ @apply bg-rose-600 text-white hover:bg-rose-700 focus:ring-rose-500/20;
120
+ }
121
+
122
+ .lesli-form-button.is-fullwidth {
123
+ @apply w-full;
124
+ }
125
+
126
+ .lesli-form-button-icon {
127
+ @apply inline-flex h-5 w-5 items-center justify-center;
128
+ }
129
+
130
+ .lesli-form-button-icon .material-symbols {
131
+ @apply text-xl leading-none;
132
+ }
133
+
134
+ .lesli-form-fieldset {
135
+ @apply mb-6 rounded-2xl border border-slate-200 bg-white p-6 shadow-sm;
136
+ }
137
+
138
+ .lesli-form-legend {
139
+ @apply -ml-1 mb-5 px-1 text-lg font-semibold text-slate-800;
140
+ }
141
+
142
+ .lesli-form-fieldset.is-info {
143
+ @apply border-sky-200 bg-sky-50/40;
144
+ }
145
+
146
+ .lesli-form-fieldset.is-success {
147
+ @apply border-emerald-200 bg-emerald-50/40;
148
+ }
149
+
150
+ .lesli-form-fieldset.is-warning {
151
+ @apply border-amber-200 bg-amber-50/50;
152
+ }
153
+
154
+ .lesli-form-fieldset.is-danger {
155
+ @apply border-rose-200 bg-rose-50/40;
156
+ }
157
+
158
+ @media (min-width: 768px) {
159
+ .lesli-form-field.is-horizontal {
160
+ @apply grid grid-cols-[minmax(8rem,1fr)_minmax(0,3fr)] items-start gap-5;
161
+ }
162
+
163
+ .lesli-form-field.is-horizontal > .lesli-form-label-wrapper {
164
+ @apply mb-0 pt-2.5 text-right;
165
+ }
166
+
167
+ .lesli-form-field.is-horizontal > .lesli-form-field-body {
168
+ @apply col-start-2;
169
+ }
170
+ }
171
+ }
@@ -31,57 +31,5 @@ Building a better future, one line of code at a time.
31
31
  */
32
32
 
33
33
 
34
- // ·
35
- .lesli-form-field {
36
- .field:not(.is-horizontal) {
37
- .field-label {
38
- text-align: left;
39
- }
40
- }
41
- }
42
-
43
-
44
- .lesli-form-fieldset {
45
- padding: 2.5rem 2rem;
46
- border-radius: 5px;
47
- //border: 1px solid #ccc;
48
- background-color: #fff;
49
- box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px;
50
-
51
- fieldset {
52
- padding: 1.2rem;
53
- border-radius: 5px;
54
- border: 1px solid #ccc;
55
- margin-bottom: 1rem;
56
- }
57
- }
58
-
59
-
60
- form fieldset.is-warning {
61
- border: 2px solid var(--bulma-warning);
62
- background-color: var(--bulma-warning-95);
63
- }
64
- form fieldset.is-warning h5 {
65
- color: var(--bulma-warning-bold);
66
- font-weight: 600;
67
- }
68
-
69
-
70
- form fieldset.is-info {
71
- border: 2px solid var(--bulma-info);
72
- background-color: var(--bulma-info-95);
73
- }
74
- form fieldset.is-info h5 {
75
- color: var(--bulma-info-bold);
76
- font-weight: 600;
77
- }
78
-
79
-
80
- form fieldset.is-danger {
81
- border: 2px solid var(--bulma-danger);
82
- background-color: var(--bulma-danger-95);
83
- }
84
- form fieldset.is-danger h5 {
85
- color: var(--bulma-danger-bold);
86
- font-weight: 600;
87
- }
34
+ // LesliView forms have migrated to Tailwind.
35
+ // See lib/lesli_view/forms/form.css.
@@ -2,17 +2,21 @@ module LesliView
2
2
  module Forms
3
3
  module Inputs
4
4
  def label(method, text = nil, options = {})
5
- super(method, text, options.merge(class: 'label'))
5
+ options = options.symbolize_keys
6
+ options[:class] = merge_css_classes("lesli-form-label", options[:class])
7
+ super(method, text, options)
6
8
  end
7
9
 
8
10
  def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
9
- value = @object.send(method)
11
+ options = options.symbolize_keys
12
+ options[:class] = merge_css_classes("lesli-form-checkbox", options[:class])
10
13
  super(method, options, checked_value, unchecked_value)
11
14
  end
12
15
 
13
16
  def text_field(method, options = {})
14
- value = @object.send(method) if @object
15
- super(method, options.merge(value: value, class: 'input'))
17
+ options = options.symbolize_keys
18
+ options[:class] = merge_css_classes("lesli-form-input", options[:class])
19
+ super(method, options)
16
20
  end
17
21
 
18
22
  def text_editor(method, options = {})
@@ -26,22 +30,30 @@ module LesliView
26
30
 
27
31
 
28
32
  def email_field(method, options = {})
29
- value = @object.send(method)
30
- super(method, options.merge(value: value, class: 'input'))
33
+ options = options.symbolize_keys
34
+ options[:class] = merge_css_classes("lesli-form-input", options[:class])
35
+ super(method, options)
31
36
  end
32
37
 
33
38
  def password_field(method, options = {})
34
- value = @object.send(method)
35
- super(method, options.merge(value: value, class: 'input'))
39
+ options = options.symbolize_keys
40
+ options[:class] = merge_css_classes("lesli-form-input", options[:class])
41
+ options[:value] = @object.public_send(method) if @object && !options.key?(:value)
42
+ super(method, options)
43
+ end
44
+
45
+ def select(method, choices = nil, options = {}, html_options = {}, &block)
46
+ html_options = html_options.symbolize_keys
47
+ html_options[:class] = merge_css_classes("lesli-form-select", html_options[:class])
48
+ super(method, choices, options, html_options, &block)
49
+ end
50
+
51
+ def submit(value = nil, options = {})
52
+ options = options.symbolize_keys
53
+ options[:class] = merge_css_classes("lesli-form-button is-primary", options[:class])
54
+ super(value, options)
36
55
  end
37
56
 
38
- def submit(value=nil, options = {})
39
- # Extract and merge classes properly
40
- default_classes = "button is-primary"
41
- custom_classes = options[:class] || ""
42
- merged_classes = "#{default_classes} #{custom_classes}".strip
43
- super(value, options.merge(class: merged_classes))
44
- end
45
57
  end
46
58
  end
47
59
  end
@@ -1,7 +1,13 @@
1
1
 
2
- <!-- app/components/lesli_application_container_component.html.erb -->
2
+ <%
3
+ container_class = class_names(
4
+ "lesli-application-container mx-auto block w-full px-4 sm:px-6 lg:px-8",
5
+ dashboard ? "container" : "max-w-7xl"
6
+ )
7
+ %>
8
+
3
9
  <turbo-frame
4
10
  id="<%= turbo_frame_id %>"
5
- class="lesli-application-container container <%= 'is-fluid' if dashboard %>">
11
+ class="<%= container_class %>">
6
12
  <%= content %>
7
13
  </turbo-frame>
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LesliView
4
- VERSION = "1.1.2"
5
- BUILD = "1777174912"
4
+ VERSION = "1.1.3"
5
+ BUILD = "1789353654"
6
6
  end
@@ -1,135 +1,44 @@
1
- <style>
2
- .lesli-view-widgets-calendar .box {
3
- padding: 14px;
4
- }
5
-
6
- .lesli-view-widgets-calendar-content .weekday,
7
- .lesli-view-widgets-calendar-content .days {
8
- display: grid;
9
- grid-template-columns: repeat(7, 1fr);
10
- }
11
-
12
- .lesli-view-widgets-calendar-content .weekday li,
13
- .lesli-view-widgets-calendar-content .days li {
14
- display: grid;
15
- }
16
-
17
- .lesli-view-widgets-calendar-content .weekday li a,
18
- .lesli-view-widgets-calendar-content .days li a {
19
- text-transform: uppercase;
20
- display: grid;
21
- place-items: center;
22
- width: 100%;
23
- height: 100%;
24
- color: #747978;
25
- font-size: 12px;
26
- }
27
-
28
- .lesli-view-widgets-calendar-content .weekday li a {
29
- margin-bottom: 8px;
30
- }
31
-
32
- .lesli-view-widgets-calendar-content .days li a {
33
- border-radius: 5px;
34
- font-weight: bold;
35
- padding: 4px 0;
36
- }
37
-
38
- .lesli-view-widgets-calendar-content .days li .selected{
39
- background-color: #F5A1A3!important;
40
- color: #ffffff;
41
- }
42
-
43
- .lesli-view-widgets-calendar-content .days li a:hover{
44
- background-color: #EEEDE9;
45
- }
46
-
47
- .lesli-view-widgets-calendar-content .days li .event{
48
- color: #F5A1A3;
49
- }
50
- </style>
51
- <div class="lesli-view-widgets-calendar">
52
- <div class="box">
53
- <div class="lesli-view-widgets-calendar-content">
54
- <ul class="weekday">
55
- <li><a href="#" title="Sun" data-value="7">Sun</a></li>
56
- <li><a href="#" title="Mon" data-value="1">Mon</a></li>
57
- <li><a href="#" title="Tue" data-value="2">Tue</a></li>
58
- <li><a href="#" title="Wed" data-value="3">Wed</a></li>
59
- <li><a href="#" title="Thu" data-value="4">Thu</a></li>
60
- <li><a href="#" title="Fri" data-value="5">Fri</a></li>
61
- <li><a href="#" title="Say" data-value="6">Sat</a></li>
62
- </ul>
63
- <ul class="days" id="lesli-view-widgets-calendar-days">
64
- </ul>
65
- </div>
1
+ <section
2
+ class="lesli-calendar-widget h-full rounded-2xl border border-slate-200 bg-white p-3 shadow-sm sm:p-4"
3
+ aria-label="<%= calendar_title %>">
4
+
5
+ <div class="grid grid-cols-7 gap-0.5">
6
+ <% weekdays.each do |abbreviated_name, full_name| %>
7
+ <div class="py-0.5 text-center text-[11px] font-semibold uppercase tracking-wide text-slate-400">
8
+ <abbr class="no-underline" title="<%= full_name %>"><%= abbreviated_name %></abbr>
9
+ </div>
10
+ <% end %>
66
11
  </div>
67
- </div>
68
- <script>
69
- (() => {
70
-
71
- const current = new Date();
72
-
73
- const daysEl = document.getElementById('lesli-view-widgets-calendar-days');
74
-
75
- // Example event days (YYYY-MM-DD)
76
- const events = new Set([
77
- '2026-01-08',
78
- '2026-01-10',
79
- '2026-01-27'
80
- ]);
81
-
82
- function renderCalendar(date) {
83
- daysEl.innerHTML = '';
84
12
 
85
- const year = date.getFullYear();
86
- const month = date.getMonth();
87
-
88
- // Monday-based week
89
- const firstDay = new Date(year, month, 1);
90
-
91
- const start = firstDay.getDay(); // 0 = Sunday, 6 = Saturday
92
-
93
- const daysInMonth = new Date(year, month + 1, 0).getDate();
94
-
95
- // Empty cells before first day
96
- for (let i = 0; i < start; i++) {
97
- daysEl.appendChild(document.createElement('li'));
98
- }
99
-
100
- // Days
101
- for (let day = 1; day <= daysInMonth; day++) {
102
- const li = document.createElement('li');
103
- const a = document.createElement('a');
104
-
105
- const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
106
-
107
- a.textContent = day;
108
- a.href = '#';
109
- a.dataset.value = day;
110
-
111
- if (events.has(dateStr)) a.classList.add('event');
112
-
113
- if (
114
- day === new Date().getDate() &&
115
- month === new Date().getMonth() &&
116
- year === new Date().getFullYear()
117
- ) {
118
- a.classList.add('selected');
119
- }
120
-
121
- a.addEventListener('click', e => {
122
- e.preventDefault();
123
- document
124
- .querySelectorAll('.days a.selected')
125
- .forEach(el => el.classList.remove('selected'));
126
- a.classList.add('selected');
127
- });
128
-
129
- li.appendChild(a);
130
- daysEl.appendChild(li);
131
- }
132
- }
133
- renderCalendar(current);
134
- })();
135
- </script>
13
+ <div class="mt-0.5 grid grid-cols-7 gap-0.5">
14
+ <% calendar_days.each do |day| %>
15
+ <% if day.nil? %>
16
+ <span class="h-8 sm:h-9" aria-hidden="true"></span>
17
+ <% else %>
18
+ <% day_date = date_for(day) %>
19
+ <div
20
+ class="relative flex h-8 items-center justify-center rounded-lg text-sm font-medium sm:h-9
21
+ <%= if selected?(day)
22
+ "bg-sky-600 text-white shadow-sm"
23
+ elsif today?(day)
24
+ "bg-sky-50 text-sky-700 ring-1 ring-inset ring-sky-200"
25
+ else
26
+ "text-slate-600"
27
+ end %>">
28
+
29
+ <time
30
+ datetime="<%= day_date.iso8601 %>"
31
+ aria-label="<%= I18n.l(day_date, format: :long) %>"
32
+ <% if today?(day) %>aria-current="date"<% end %>><%= day %></time>
33
+
34
+ <% if event?(day) %>
35
+ <span
36
+ class="absolute bottom-1 h-1 w-1 rounded-full <%= selected?(day) ? 'bg-white' : 'bg-rose-400' %>"
37
+ aria-hidden="true"></span>
38
+ <span class="sr-only"><%= I18n.t("date.has_events", default: "Has events") %></span>
39
+ <% end %>
40
+ </div>
41
+ <% end %>
42
+ <% end %>
43
+ </div>
44
+ </section>
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  =begin
2
4
 
3
5
  Lesli
@@ -33,11 +35,83 @@ Building a better future, one line of code at a time.
33
35
  module LesliView
34
36
  module Widgets
35
37
  class Calendar < ViewComponent::Base
36
- attr_reader :title, :number
38
+ WEEK_STARTS = {
39
+ sunday: 0,
40
+ monday: 1
41
+ }.freeze
42
+
43
+ attr_reader :title, :number, :date, :selected_date, :week_starts_on
37
44
 
38
- def initialize(title=nil, number=nil)
45
+ def initialize(
46
+ title = nil,
47
+ number = nil,
48
+ date: ::Date.current,
49
+ events: [],
50
+ selected_date: ::Date.current,
51
+ week_starts_on: :sunday
52
+ )
39
53
  @title = title
40
- @number = number
54
+ @number = number # Retained for compatibility with the original positional API.
55
+ @date = normalize_date(date) || ::Date.current
56
+ @selected_date = normalize_date(selected_date)
57
+ @week_starts_on = normalize_week_start(week_starts_on)
58
+ @event_dates = Array(events).each_with_object({}) do |event, dates|
59
+ event_date = normalize_date(event)
60
+ dates[event_date] = true if event_date
61
+ end
62
+ end
63
+
64
+ def calendar_title
65
+ title.presence || I18n.l(date, format: "%B %Y")
66
+ end
67
+
68
+ def weekdays
69
+ abbreviated = Array(I18n.t("date.abbr_day_names"))
70
+ full = Array(I18n.t("date.day_names"))
71
+ offset = WEEK_STARTS.fetch(week_starts_on)
72
+
73
+ abbreviated.zip(full).rotate(offset)
74
+ end
75
+
76
+ def calendar_days
77
+ first_day = date.beginning_of_month
78
+ leading_blanks = (first_day.wday - WEEK_STARTS.fetch(week_starts_on)) % 7
79
+ days = Array.new(leading_blanks) + (1..date.end_of_month.day).to_a
80
+ days.concat(Array.new((7 - (days.length % 7)) % 7))
81
+ end
82
+
83
+ def date_for(day)
84
+ date.change(day: day)
85
+ end
86
+
87
+ def selected?(day)
88
+ selected_date == date_for(day)
89
+ end
90
+
91
+ def today?(day)
92
+ ::Date.current == date_for(day)
93
+ end
94
+
95
+ def event?(day)
96
+ @event_dates.key?(date_for(day))
97
+ end
98
+
99
+ private
100
+
101
+ def normalize_date(value)
102
+ return if value.nil?
103
+ return value.to_date if value.respond_to?(:to_date)
104
+
105
+ ::Date.parse(value.to_s)
106
+ rescue ArgumentError, TypeError
107
+ nil
108
+ end
109
+
110
+ def normalize_week_start(value)
111
+ value = value.to_s.downcase.to_sym
112
+ return value if WEEK_STARTS.key?(value)
113
+
114
+ :sunday
41
115
  end
42
116
  end
43
117
  end
@@ -1,28 +1,27 @@
1
- <style>
2
- .lesli-view-widgets-chart {
3
- }
4
- </style>
1
+ <section
2
+ class="lesli-chart-widget h-full rounded-2xl border border-slate-200 bg-white p-4 shadow-sm"
3
+ <% if title.present? %>aria-labelledby="<%= title_id %>"<% else %>aria-label="<%= I18n.t('charts.chart', default: 'Chart') %>"<% end %>>
5
4
 
6
- <%
7
- # Determine the chart component class dynamically
8
- chart_component = case type
9
- when :bar
10
- LesliView::Charts::Bar
11
- when :line
12
- LesliView::Charts::Line
13
- else
14
- nil # Handle unknown types gracefully
15
- end
16
- %>
5
+ <div class="flex h-full min-h-40 flex-col">
6
+ <% if title.present? %>
7
+ <h2 id="<%= title_id %>" class="mb-3 text-sm font-medium leading-5 text-slate-500">
8
+ <%= title %>
9
+ </h2>
10
+ <% end %>
17
11
 
18
- <div class="lesli-view-widgets-chart">
19
- <% if chart_component %>
20
- <%= render(chart_component.new(
21
- title: title,
22
- labels: data.map{ |i| i[0] },
23
- dataset: data.map{ |i| i[1] },
24
- height: '132px',
25
- compact: true
26
- )) %>
27
- <% end %>
28
- </div>
12
+ <% if data? %>
13
+ <div class="min-h-0 flex-1">
14
+ <%= render(chart_component.new(
15
+ labels: labels,
16
+ datasets: [{ label: title, data: dataset }],
17
+ height: "10rem",
18
+ compact: true
19
+ )) %>
20
+ </div>
21
+ <% else %>
22
+ <div class="flex flex-1 items-center justify-center text-sm text-slate-400">
23
+ <%= I18n.t("charts.empty", default: "No chart data") %>
24
+ </div>
25
+ <% end %>
26
+ </div>
27
+ </section>