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,357 @@
1
+ /*!
2
+
3
+ Lesli
4
+
5
+ Copyright (c) 2026, Lesli Technologies, S. A.
6
+
7
+ This program is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program. If not, see http://www.gnu.org/licenses/.
19
+
20
+ Lesli · Ruby on Rails SaaS Development Framework.
21
+
22
+ Made with ♥ by LesliTech
23
+ Building a better future, one line of code at a time.
24
+
25
+ @contact hello@lesli.tech
26
+ @website https://www.lesli.tech
27
+ @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
28
+
29
+ // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
30
+ // ·
31
+ */
32
+
33
+
34
+ @layer components {
35
+ .lesli-button:hover {
36
+ cursor: pointer;
37
+ }
38
+ .lesli-button {
39
+ @apply
40
+ cursor-pointer
41
+ inline-flex
42
+ items-center
43
+ justify-center
44
+ gap-2
45
+ border
46
+ rounded-lg
47
+ h-10
48
+ px-4
49
+ text-sm
50
+ font-medium
51
+ whitespace-nowrap
52
+ transition-colors
53
+ duration-150
54
+ focus:outline-none
55
+ focus-visible:ring-2
56
+ focus-visible:ring-offset-2
57
+ disabled:pointer-events-none
58
+ disabled:opacity-50;
59
+ }
60
+
61
+ .lesli-button.is-small {
62
+ @apply
63
+ h-8
64
+ rounded-md
65
+ px-3
66
+ text-xs;
67
+ }
68
+
69
+ .lesli-button.is-icon-only {
70
+ @apply
71
+ w-10
72
+ px-0;
73
+ }
74
+
75
+ .lesli-button.is-icon-only.is-small {
76
+ @apply
77
+ w-8
78
+ px-0;
79
+ }
80
+
81
+ .lesli-button.is-loading {
82
+ @apply
83
+ pointer-events-none
84
+ cursor-wait
85
+ opacity-80;
86
+ }
87
+
88
+ .lesli-button.is-loading::before {
89
+ width: 1rem;
90
+ height: 1rem;
91
+ content: "";
92
+ border: 2px solid currentColor;
93
+ border-right-color: transparent;
94
+ border-radius: 9999px;
95
+ animation: lesli-button-spin .65s linear infinite;
96
+ }
97
+
98
+ /*
99
+ * Solid variants
100
+ */
101
+
102
+ .lesli-button.is-primary {
103
+ @apply
104
+ border-blue-600
105
+ bg-blue-600
106
+ text-white
107
+ hover:border-blue-700
108
+ hover:bg-blue-700
109
+ focus-visible:ring-blue-500;
110
+ }
111
+
112
+ .lesli-button.is-info {
113
+ @apply
114
+ border-sky-600
115
+ bg-sky-600
116
+ text-white
117
+ hover:border-sky-700
118
+ hover:bg-sky-700
119
+ focus-visible:ring-sky-500;
120
+ }
121
+
122
+ .lesli-button.is-success {
123
+ @apply
124
+ border-emerald-600
125
+ bg-emerald-600
126
+ text-white
127
+ hover:border-emerald-700
128
+ hover:bg-emerald-700
129
+ focus-visible:ring-emerald-500;
130
+ }
131
+
132
+ .lesli-button.is-warning {
133
+ @apply
134
+ border-amber-500
135
+ bg-amber-500
136
+ text-amber-950
137
+ hover:border-amber-600
138
+ hover:bg-amber-600
139
+ focus-visible:ring-amber-500;
140
+ }
141
+
142
+ .lesli-button.is-danger {
143
+ @apply
144
+ border-rose-600
145
+ bg-rose-600
146
+ text-white
147
+ hover:border-rose-700
148
+ hover:bg-rose-700
149
+ focus-visible:ring-rose-500;
150
+ }
151
+
152
+ /*
153
+ * Light variants
154
+ *
155
+ * These must come after solid variants.
156
+ */
157
+
158
+ .lesli-button.is-primary.is-light {
159
+ @apply
160
+ border-blue-100
161
+ bg-blue-50
162
+ text-blue-700
163
+ hover:border-blue-200
164
+ hover:bg-blue-100
165
+ focus-visible:ring-blue-500;
166
+ }
167
+
168
+ .lesli-button.is-info.is-light {
169
+ @apply
170
+ border-sky-100
171
+ bg-sky-50
172
+ text-sky-700
173
+ hover:border-sky-200
174
+ hover:bg-sky-100
175
+ focus-visible:ring-sky-500;
176
+ }
177
+
178
+ .lesli-button.is-success.is-light {
179
+ @apply
180
+ border-emerald-100
181
+ bg-emerald-50
182
+ text-emerald-700
183
+ hover:border-emerald-200
184
+ hover:bg-emerald-100
185
+ focus-visible:ring-emerald-500;
186
+ }
187
+
188
+ .lesli-button.is-warning.is-light {
189
+ @apply
190
+ border-amber-100
191
+ bg-amber-50
192
+ text-amber-700
193
+ hover:border-amber-200
194
+ hover:bg-amber-100
195
+ focus-visible:ring-amber-500;
196
+ }
197
+
198
+ .lesli-button.is-danger.is-light {
199
+ @apply
200
+ border-rose-100
201
+ bg-rose-50
202
+ text-rose-700
203
+ hover:border-rose-200
204
+ hover:bg-rose-100
205
+ focus-visible:ring-rose-500;
206
+ }
207
+
208
+ /*
209
+ * Outlined variants
210
+ *
211
+ * These must come after solid and light variants.
212
+ */
213
+
214
+ .lesli-button.is-primary.is-outlined {
215
+ @apply
216
+ border-blue-300
217
+ bg-transparent
218
+ text-blue-700
219
+ hover:border-blue-400
220
+ hover:bg-blue-50
221
+ focus-visible:ring-blue-500;
222
+ }
223
+
224
+ .lesli-button.is-info.is-outlined {
225
+ @apply
226
+ border-sky-300
227
+ bg-transparent
228
+ text-sky-700
229
+ hover:border-sky-400
230
+ hover:bg-sky-50
231
+ focus-visible:ring-sky-500;
232
+ }
233
+
234
+ .lesli-button.is-success.is-outlined {
235
+ @apply
236
+ border-emerald-300
237
+ bg-transparent
238
+ text-emerald-700
239
+ hover:border-emerald-400
240
+ hover:bg-emerald-50
241
+ focus-visible:ring-emerald-500;
242
+ }
243
+
244
+ .lesli-button.is-warning.is-outlined {
245
+ @apply
246
+ border-amber-300
247
+ bg-transparent
248
+ text-amber-700
249
+ hover:border-amber-400
250
+ hover:bg-amber-50
251
+ focus-visible:ring-amber-500;
252
+ }
253
+
254
+ .lesli-button.is-danger.is-outlined {
255
+ @apply
256
+ border-rose-300
257
+ bg-transparent
258
+ text-rose-700
259
+ hover:border-rose-400
260
+ hover:bg-rose-50
261
+ focus-visible:ring-rose-500;
262
+ }
263
+
264
+ /*
265
+ * Optional: light + outlined
266
+ */
267
+
268
+ .lesli-button.is-light.is-outlined {
269
+ @apply
270
+ bg-transparent;
271
+ }
272
+ }
273
+
274
+ @keyframes lesli-button-spin {
275
+ to {
276
+ transform: rotate(360deg);
277
+ }
278
+ }
279
+
280
+
281
+
282
+ @layer components {
283
+ .lesli-icon {
284
+ @apply
285
+ inline-flex
286
+ items-center
287
+ justify-center
288
+ shrink-0
289
+ h-6
290
+ w-6
291
+ text-[1.5rem]
292
+ leading-none;
293
+ }
294
+
295
+ .lesli-icon.is-small {
296
+ @apply
297
+ h-4
298
+ w-4
299
+ text-[1rem];
300
+ }
301
+
302
+ .lesli-icon.is-medium {
303
+ @apply
304
+ h-8
305
+ w-8
306
+ text-[2rem];
307
+ }
308
+
309
+ .lesli-icon.is-large {
310
+ @apply
311
+ h-12
312
+ w-12
313
+ text-[3rem];
314
+ }
315
+
316
+ .lesli-icon .material-symbols,
317
+ .lesli-icon .material-symbols-outlined,
318
+ .lesli-icon .material-symbols-rounded,
319
+ .lesli-icon .material-symbols-sharp {
320
+ @apply
321
+ block
322
+ text-inherit
323
+ leading-none;
324
+ }
325
+
326
+ /*
327
+ * Button-specific icon sizing.
328
+ * This keeps icons visually balanced inside .lesli-button.
329
+ */
330
+ .lesli-button .lesli-icon {
331
+ @apply
332
+ h-5
333
+ w-5
334
+ text-[1.25rem];
335
+ }
336
+
337
+ .lesli-button.is-small .lesli-icon {
338
+ @apply
339
+ h-4
340
+ w-4
341
+ text-[1rem];
342
+ }
343
+
344
+ .lesli-button.is-icon-only .lesli-icon {
345
+ @apply
346
+ h-5
347
+ w-5
348
+ text-[1.25rem];
349
+ }
350
+
351
+ .lesli-button.is-icon-only.is-small .lesli-icon {
352
+ @apply
353
+ h-4
354
+ w-4
355
+ text-[1rem];
356
+ }
357
+ }
@@ -6,29 +6,32 @@
6
6
  @url,
7
7
  method: @method,
8
8
  params: @params,
9
- class: html_classes,
10
- data: { turbo_frame: "_top" },
11
- **alpine_attributes) do %>
9
+ disabled: disabled?,
10
+ **common_attributes
11
+ ) do %>
12
12
  <%= button_content %>
13
13
  <% end %>
14
14
 
15
15
  <% elsif mode == :link %>
16
16
 
17
- <%= link_to(
18
- @url,
19
- class: html_classes,
20
- data: { turbo_frame: "_top" },
21
- **alpine_attributes) do %>
22
- <%= button_content %>
17
+ <% if disabled? %>
18
+ <%= content_tag(:span, role: "link", tabindex: -1, **common_attributes) do %>
19
+ <%= button_content %>
20
+ <% end %>
21
+ <% else %>
22
+ <%= link_to(@url, **common_attributes) do %>
23
+ <%= button_content %>
24
+ <% end %>
23
25
  <% end %>
24
26
 
25
27
  <% else %>
26
28
 
27
29
  <%= content_tag(
28
30
  :button,
29
- type: "button",
30
- class: html_classes,
31
- **alpine_attributes) do %>
31
+ type: button_type,
32
+ disabled: disabled?,
33
+ **common_attributes
34
+ ) do %>
32
35
  <%= button_content %>
33
36
  <% end %>
34
37
 
@@ -35,33 +35,38 @@ Building a better future, one line of code at a time.
35
35
  module LesliView
36
36
  module Elements
37
37
  class Button < ViewComponent::Base
38
- attr_reader :label, :url, :icon, :loading, :solid, :info, :success, :warning, :danger, :small, :dispatch, :method, :params, :css_class
38
+ attr_reader :label, :url, :icon, :loading, :solid, :light,
39
+ :info, :success, :warning, :danger, :small, :dispatch,
40
+ :method, :params, :css_class, :aria_label, :disabled,
41
+ :button_type, :data
39
42
 
40
- # Adds two numbers together.
41
- # @param [Integer] a The first number.
42
- # @param [Integer] b The second number.
43
- # @return [Integer] The sum of the two numbers.
44
43
  def initialize(
45
- label=nil,
46
- url:nil,
47
- icon: nil,
48
- loading: false,
49
- solid: false,
50
- info: false,
51
- success: false,
52
- warning: false,
53
- danger: false,
44
+ label = nil,
45
+ url: nil,
46
+ icon: nil,
47
+ loading: false,
48
+ solid: true,
49
+ light: false,
50
+ info: true,
51
+ success: false,
52
+ warning: false,
53
+ danger: false,
54
54
  small: false,
55
- dispatch:nil,
55
+ dispatch: nil,
56
56
  method: nil,
57
- params:nil,
58
- css_class:nil
57
+ params: nil,
58
+ css_class: nil,
59
+ aria_label: nil,
60
+ disabled: false,
61
+ type: :button,
62
+ data: {}
59
63
  )
60
64
  @label = label
61
65
  @url = url
62
66
  @icon = icon
63
67
  @loading = loading
64
68
  @solid = solid
69
+ @light = light
65
70
  @info = info
66
71
  @success = success
67
72
  @warning = warning
@@ -71,6 +76,10 @@ module LesliView
71
76
  @method = method
72
77
  @params = params
73
78
  @css_class = css_class
79
+ @aria_label = aria_label
80
+ @disabled = disabled
81
+ @button_type = type
82
+ @data = data
74
83
  end
75
84
 
76
85
  def mode
@@ -80,20 +89,22 @@ module LesliView
80
89
  end
81
90
 
82
91
  def alpine_attributes
83
- return {} unless @dispatch
92
+ return {} unless dispatch.present?
84
93
 
85
94
  {
86
- "x-data": "",
87
- "@click": "$dispatch('#{@dispatch}')"
95
+ "x-data": "",
96
+ "@click": "$dispatch('#{dispatch}')"
88
97
  }
89
98
  end
90
99
 
91
100
  def html_classes
92
- classes = ["button", button_variant]
93
- classes << "is-light" << "is-outlined" unless solid
101
+ classes = ["lesli-button", button_variant]
102
+ classes << "is-outlined" unless solid
103
+ classes << "is-light" if light
94
104
  classes << "is-loading" if loading
95
105
  classes << "is-small" if small
96
- classes << css_class
106
+ classes << "is-icon-only" if icon_only?
107
+ classes << css_class if css_class.present?
97
108
  classes.join(" ")
98
109
  end
99
110
 
@@ -106,21 +117,50 @@ module LesliView
106
117
  end
107
118
 
108
119
  def icon_only?
109
- icon && !label
120
+ icon.present? && visible_label.blank?
121
+ end
122
+
123
+ def disabled?
124
+ disabled || loading
125
+ end
126
+
127
+ def visible_label
128
+ label.presence || (content if content?)
129
+ end
130
+
131
+ def accessible_label
132
+ aria_label.presence || (icon.to_s.humanize if icon_only?)
133
+ end
134
+
135
+ def html_data
136
+ { turbo_frame: "_top" }.merge(data || {})
137
+ end
138
+
139
+ def common_attributes
140
+ attributes = {
141
+ class: html_classes,
142
+ data: html_data,
143
+ aria: {
144
+ label: accessible_label,
145
+ busy: loading || nil,
146
+ disabled: disabled? || nil
147
+ }.compact
148
+ }
149
+
150
+ attributes.merge(alpine_attributes)
110
151
  end
111
152
 
112
153
  def button_content
113
- if @icon
114
- safe_join([
115
- content_tag(:span,
116
- content_tag(:span, @icon, class: "material-symbols"),
117
- class: "icon #{'is-small' if @small}"
118
- ),
119
- (@icon_only ? nil : content_tag(:span, @label))
120
- ].compact)
121
- else
122
- @label
154
+ parts = []
155
+
156
+ if icon.present?
157
+ parts << content_tag(:span, class: "lesli-icon", aria: { hidden: true }) do
158
+ content_tag(:span, icon, class: "material-symbols")
159
+ end
123
160
  end
161
+
162
+ parts << content_tag(:span, visible_label) if visible_label.present?
163
+ safe_join(parts)
124
164
  end
125
165
  end
126
166
  end
@@ -1,8 +1,33 @@
1
1
 
2
- <div class="lesli-empty has-text-centered">
3
- <svg width="184" height="184" viewBox="0 0 184 184" fill="none" xmlns="http://www.w3.org/2000/svg">
4
- <path d="M145.271 90.6522H142.735V89.528C142.735 83.9793 138.222 79.4655 132.673 79.4655H85.7357C84.255 79.4655 82.1132 78.0942 81.4922 76.7515L76.1878 65.2544C74.1523 60.8499 68.9629 57.5293 64.1128 57.5293H39.7012C34.1524 57.5293 29.6387 62.043 29.6387 67.5918V151.809C29.6387 157.358 34.1524 161.872 39.7012 161.872H53.3459C53.5357 161.883 53.7139 161.932 53.9065 161.932H112.945V153.307H108.831H53.9065C53.7197 153.307 53.6219 153.278 53.5587 153.247C53.527 153.232 53.4897 153.215 53.481 153.206C53.458 153.181 53.412 153.045 53.4609 152.772L63.1985 101.419C63.4027 100.338 64.6849 99.2772 65.7803 99.2772H134.11H142.735H145.271C145.553 99.2772 145.679 99.349 145.697 99.3749C145.714 99.4008 145.766 99.533 145.714 99.809L142.735 115.524L135.979 151.159C135.775 152.24 134.493 153.301 133.395 153.301H121.964V161.926H133.395C138.621 161.926 143.48 157.904 144.455 152.767L154.189 101.413C154.713 98.6648 154.037 95.9163 152.341 93.8693C150.645 91.8281 148.069 90.6522 145.271 90.6522ZM134.11 90.6522H65.7832C60.5593 90.6522 55.7005 94.6743 54.7259 99.8119L44.9883 151.168C44.856 151.864 44.8043 152.56 44.8244 153.247H39.7012C38.922 153.247 38.2637 152.588 38.2637 151.809V67.5918C38.2637 66.8127 38.922 66.1543 39.7012 66.1543H64.1128C65.5934 66.1543 67.7353 67.5257 68.3563 68.8683L73.6607 80.3654C75.6962 84.7699 80.8855 88.0905 85.7357 88.0905H132.673C133.452 88.0905 134.11 88.7489 134.11 89.528V90.6522Z" fill="#BDBDBD"/>
5
- <path d="M145.7 50.5227L153.201 43.0219C154.805 41.4176 154.739 38.7525 153.054 37.0677C151.369 35.383 148.704 35.3197 147.1 36.9211L139.599 44.422L132.098 36.9211C130.494 35.3169 127.829 35.383 126.144 37.0677C124.459 38.7525 124.396 41.4176 125.997 43.0219L133.498 50.5227L125.997 58.0236C124.393 59.6279 124.459 62.293 126.144 63.9777C127.829 65.6625 130.494 65.7257 132.098 64.1244L139.599 56.6235L147.1 64.1244C148.704 65.7286 151.369 65.6625 153.054 63.9777C154.739 62.293 154.802 59.6279 153.201 58.0236L145.7 50.5227Z" fill="#BDBDBD"/>
6
- </svg>
7
- <p class="has-text-grey"><%= @text %></p>
2
+ <div
3
+ class="lesli-empty-state flex w-full flex-col items-center justify-center px-6 py-10 text-center sm:py-12"
4
+ role="region"
5
+ aria-label="<%= text.presence || "Empty state" %>">
6
+
7
+ <div class="mb-5 flex h-36 w-36 items-center justify-center rounded-full bg-slate-100/80 sm:h-40 sm:w-40">
8
+ <svg
9
+ class="h-28 w-28 text-slate-300 sm:h-32 sm:w-32"
10
+ viewBox="0 0 184 184"
11
+ fill="none"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ aria-hidden="true"
14
+ focusable="false">
15
+ <path d="M145.271 90.6522H142.735V89.528C142.735 83.9793 138.222 79.4655 132.673 79.4655H85.7357C84.255 79.4655 82.1132 78.0942 81.4922 76.7515L76.1878 65.2544C74.1523 60.8499 68.9629 57.5293 64.1128 57.5293H39.7012C34.1524 57.5293 29.6387 62.043 29.6387 67.5918V151.809C29.6387 157.358 34.1524 161.872 39.7012 161.872H53.3459C53.5357 161.883 53.7139 161.932 53.9065 161.932H112.945V153.307H108.831H53.9065C53.7197 153.307 53.6219 153.278 53.5587 153.247C53.527 153.232 53.4897 153.215 53.481 153.206C53.458 153.181 53.412 153.045 53.4609 152.772L63.1985 101.419C63.4027 100.338 64.6849 99.2772 65.7803 99.2772H134.11H142.735H145.271C145.553 99.2772 145.679 99.349 145.697 99.3749C145.714 99.4008 145.766 99.533 145.714 99.809L142.735 115.524L135.979 151.159C135.775 152.24 134.493 153.301 133.395 153.301H121.964V161.926H133.395C138.621 161.926 143.48 157.904 144.455 152.767L154.189 101.413C154.713 98.6648 154.037 95.9163 152.341 93.8693C150.645 91.8281 148.069 90.6522 145.271 90.6522ZM134.11 90.6522H65.7832C60.5593 90.6522 55.7005 94.6743 54.7259 99.8119L44.9883 151.168C44.856 151.864 44.8043 152.56 44.8244 153.247H39.7012C38.922 153.247 38.2637 152.588 38.2637 151.809V67.5918C38.2637 66.8127 38.922 66.1543 39.7012 66.1543H64.1128C65.5934 66.1543 67.7353 67.5257 68.3563 68.8683L73.6607 80.3654C75.6962 84.7699 80.8855 88.0905 85.7357 88.0905H132.673C133.452 88.0905 134.11 88.7489 134.11 89.528V90.6522Z" fill="currentColor"/>
16
+ <path d="M145.7 50.5227L153.201 43.0219C154.805 41.4176 154.739 38.7525 153.054 37.0677C151.369 35.383 148.704 35.3197 147.1 36.9211L139.599 44.422L132.098 36.9211C130.494 35.3169 127.829 35.383 126.144 37.0677C124.459 38.7525 124.396 41.4176 125.997 43.0219L133.498 50.5227L125.997 58.0236C124.393 59.6279 124.459 62.293 126.144 63.9777C127.829 65.6625 130.494 65.7257 132.098 64.1244L139.599 56.6235L147.1 64.1244C148.704 65.7286 151.369 65.6625 153.054 63.9777C154.739 62.293 154.802 59.6279 153.201 58.0236L145.7 50.5227Z" fill="currentColor"/>
17
+ </svg>
18
+ </div>
19
+
20
+ <% if text.present? %>
21
+ <p class="text-base font-semibold text-slate-700 sm:text-lg"><%= text %></p>
22
+ <% end %>
23
+
24
+ <% if description.present? %>
25
+ <p class="mt-2 max-w-md text-sm leading-6 text-slate-500"><%= description %></p>
26
+ <% end %>
27
+
28
+ <% if content? %>
29
+ <div class="mt-6 flex flex-wrap items-center justify-center gap-3">
30
+ <%= content %>
31
+ </div>
32
+ <% end %>
8
33
  </div>
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  =begin
2
4
 
3
5
  Lesli
@@ -32,8 +34,13 @@ Building a better future, one line of code at a time.
32
34
  module LesliView
33
35
  module Elements
34
36
  class Empty < ViewComponent::Base
35
- def initialize(text: "No data found")
37
+ attr_reader :text, :description
38
+
39
+ # `text` remains optional for icon-only uses such as error pages.
40
+ # A component block can be supplied to render an action below the message.
41
+ def initialize(text: "No data found", description: nil)
36
42
  @text = text
43
+ @description = description
37
44
  end
38
45
  end
39
46
  end
@@ -0,0 +1,42 @@
1
+ distribution: [{
2
+ label: "Deactivated_users",
3
+ value: summary[:total_users_deactivated],
4
+ percentage: percentage(summary[:total_users_deactivated], summary[:total_users]),
5
+ color: "#5D7C4D"
6
+ }, {
7
+ label: "Locked_users",
8
+ value: summary[:total_users_locked],
9
+ percentage: percentage(summary[:total_users_locked], summary[:total_users]),
10
+ color: "#B23A3A"
11
+ }, {
12
+ label: "Unconfirmed_users",
13
+ value: summary[:total_users_unconfirmed],
14
+ percentage: percentage(summary[:total_users_unconfirmed], summary[:total_users]),
15
+ color: "#5F6C7D"
16
+ }, {
17
+ label: "Deleted_users",
18
+ value: summary[:total_users_deleted],
19
+ percentage: percentage(summary[:total_users_deleted], summary[:total_users]),
20
+ color: "#4B5FA8"
21
+ }],
22
+ <%= render(LesliView::Elements::Table.new(columns: [])) do |table| %>
23
+ <% @users[:distribution].each do |user| %>
24
+ <% table.with_row do |row| %>
25
+ <% row.with_cell do %>
26
+ <%= user[:label] %>
27
+ <% end %>
28
+ <% row.with_cell(class_name: "w-full p-0") do %>
29
+ <p style="padding:8px;border-radius:4px;background:<%= user[:color] %>;width:<%= user[:percentage] %>%">
30
+ </p>
31
+ <% end %>
32
+ <% row.with_cell(align: :right) do %>
33
+ <%= user[:value] %>
34
+ <% end %>
35
+ <% row.with_cell(align: :right) do %>
36
+ <span class="font-semibold text-slate-400">
37
+ <%= user[:percentage] %>%
38
+ </span>
39
+ <% end %>
40
+ <% end %>
41
+ <% end %>
42
+ <% end %>