lato 3.26.0 → 3.26.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4fbacf743cd38a4c4b42cc018a23fa40107f40d08d076ca05a5cd2ac539f84f2
4
- data.tar.gz: 54c1b642c8aa9b42136b773067207b36cb2760b4695e5b8ecb073e8f798e830d
3
+ metadata.gz: 59109ceb11a3fb4c78126d99664864266b1d25ac77ff36ef8f6cf3bc5a66ab18
4
+ data.tar.gz: 7ca6befa9186b81529841c3b25409396beb26b154f99c605557c0b1106e32594
5
5
  SHA512:
6
- metadata.gz: f1605ffef039889624f834a870709c910dbb614a78868cf4c0a84d0925dc7f7c72f8b1db8a6c0527ea422c327dec75c58b27a342a32e9ae49d5b4ceedcbf0210
7
- data.tar.gz: 52fc4da5a54c9d35f2dd4fd324f20e750496d1bf7bf8b787129795b66d565c68742b1ec2803e87bf1c055a54760a216b72e60d78af61a256b6c18fc3c50042a7
6
+ metadata.gz: 33934d542029ce620a7ff4e06c03a2eab2bc7fff362d07237641329724cdac95541b6ea98fb0ad171d84a007705593896a796d2e7648387c4269b22c6812665c
7
+ data.tar.gz: 5797d50477282510f006e201009aa8c0e7b21dd6c7cadf5e80c0dfbbda8eabbadf88669a06f7425d1d95953ac8ffa2a8ae809dd1333959849805491d3cec6bf8
data/README.md CHANGED
@@ -8,6 +8,7 @@ This gem includes:
8
8
  - User management with **full authentication system** (signin, signup, recover password, email validation, Google Authenticator and WebAuthn/passkey multi-factor authentication, ETH wallet connection) and **account management** (user settings, password change, email change, etc.);
9
9
  - **Web interface** built with [Bootstrap](https://getbootstrap.com/) and [Bootstrap Icons](https://icons.getbootstrap.com/);
10
10
  - **UI components integrated with Rails** (data tables, forms, modals, async job executions etc.);
11
+ - **Accessibility built in**: labelled form fields with linked error messages, keyboard friendly modals with focus return, skip link, `aria-sort` on tables, live regions for feedback and WCAG 2.2 target sizes. Core pages report zero axe-core violations on WCAG 2.2 AA rules;
11
12
 
12
13
  The gem is **agnostic to the application stack**. You can use it with different databases (PostgreSQL, MySQL, SQLite), different Job processors (Solid queue, Sidekiq, Delayed Job, etc.), different cache systems (Redis, Memcached, etc.).
13
14
 
@@ -38,6 +38,20 @@ $tooltip-max-width: 300px;
38
38
  margin: 0 4px;
39
39
  }
40
40
 
41
+ // WCAG 2.2 target size (2.5.8): interactive targets of at least 24x24px.
42
+ .pagination > span > a {
43
+ display: inline-flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ min-width: 24px;
47
+ min-height: 24px;
48
+ }
49
+
50
+ // Bootstrap's pink code color fails contrast on tinted alert backgrounds.
51
+ .alert code {
52
+ color: inherit;
53
+ }
54
+
41
55
  .pagination .current {
42
56
  font-weight: bold;
43
57
  }
@@ -143,6 +157,9 @@ main, aside {
143
157
  .btn {
144
158
  font-size: 12px;
145
159
  padding: 0.15rem 0.3rem;
160
+ // 25px: btn-group overlaps adjacent buttons by 1px, the visible target must stay >= 24px (WCAG 2.5.8)
161
+ min-width: 25px;
162
+ min-height: 24px;
146
163
  }
147
164
  }
148
165
 
@@ -284,11 +284,20 @@ module Lato
284
284
  form.submit label, options
285
285
  end
286
286
 
287
+ # Kaminari labels every paginator "pager": several index tables on one page would expose
288
+ # identical navigation landmarks, so each one gets a label from its index key (unique per page).
289
+ def lato_index_paginate(collection, key, **options)
290
+ name = key.to_s == 'default' ? collection.model.model_name.human(count: 2) : key.to_s.humanize
291
+ label = I18n.t('lato.pagination_label', name: name)
292
+ paginate(collection, param_name: "#{key}_page", **options)
293
+ .to_str.sub('aria-label="pager"', "aria-label=\"#{ERB::Util.html_escape(label)}\"").html_safe
294
+ end
295
+
287
296
  # Data
288
297
  ##
289
298
 
290
299
  def lato_data_badge(label, color = 'primary')
291
- content_tag :span, label, class: "badge rounded-pill bg-#{color}"
300
+ content_tag :span, label, class: "badge rounded-pill text-bg-#{color}"
292
301
  end
293
302
 
294
303
  def lato_data_user(label, image_url = nil)
@@ -30,7 +30,7 @@ user ||= Lato::User.new
30
30
  </div>
31
31
  <% elsif session[:web3_nonce] %>
32
32
  <div class="alert alert-light mb-0">
33
- <h4 class="alert-heading" role="status"><%= I18n.t('lato.connecting') %></h4>
33
+ <h3 class="alert-heading h4" role="status"><%= I18n.t('lato.connecting') %></h3>
34
34
  <div class="progress" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100">
35
35
  <div class="progress-bar progress-bar-striped progress-bar-animated" style="width: 100%"></div>
36
36
  </div>
@@ -73,7 +73,7 @@ user ||= Lato::User.new
73
73
  </script>
74
74
  <% else %>
75
75
  <div class="alert alert-light mb-0">
76
- <h4 class="alert-heading"><%= I18n.t('lato.account_web3_start_title') %></h4>
76
+ <h3 class="alert-heading h4"><%= I18n.t('lato.account_web3_start_title') %></h3>
77
77
  <p>
78
78
  <%= raw I18n.t('lato.account_web3_start_description') %>
79
79
  </p>
@@ -20,7 +20,7 @@ end
20
20
 
21
21
  <% if user.webauthn_enabled? %>
22
22
  <div class="alert alert-success mb-3">
23
- <h4 class="alert-heading"><%= I18n.t('lato.account_webauthn_ready_title') %></h4>
23
+ <h3 class="alert-heading h4"><%= I18n.t('lato.account_webauthn_ready_title') %></h3>
24
24
  <p class="mb-0">
25
25
  <%= raw I18n.t('lato.account_webauthn_ready_description') %>
26
26
  </p>
@@ -36,7 +36,7 @@ end
36
36
  <%= form.hidden_field :webauthn_credential, data: { lato_account_webauthn_target: 'credentialInput' } %>
37
37
 
38
38
  <div class="alert alert-light mb-3" data-lato-account-webauthn-target="status">
39
- <h4 class="alert-heading"><%= I18n.t('lato.account_webauthn_in_progress_title') %></h4>
39
+ <h3 class="alert-heading h4"><%= I18n.t('lato.account_webauthn_in_progress_title') %></h3>
40
40
  <p class="mb-0">
41
41
  <%= raw I18n.t('lato.account_webauthn_in_progress_description') %>
42
42
  </p>
@@ -53,7 +53,7 @@ end
53
53
  </div>
54
54
  <% else %>
55
55
  <div class="alert alert-light mb-0">
56
- <h4 class="alert-heading"><%= I18n.t('lato.account_webauthn_start_title') %></h4>
56
+ <h3 class="alert-heading h4"><%= I18n.t('lato.account_webauthn_start_title') %></h3>
57
57
  <p>
58
58
  <%= raw I18n.t('lato.account_webauthn_start_description') %>
59
59
  </p>
@@ -1,9 +1,12 @@
1
+ <%# Rendered in several account forms on the same page: suffix ids so each label targets its own field. %>
2
+ <% password_id = form.field_id(:current_password, form.object_id) %>
3
+ <% code_id = form.field_id(:current_authenticator_code, form.object_id) %>
1
4
  <div class="mb-3" data-controller="lato-account-security" data-lato-account-security-challenge-url-value="<%= lato.account_security_challenge_path %>" data-lato-account-security-webauthn-value="<%= user.webauthn_enabled? && !user.authenticator_enabled? %>">
2
- <%= form.label :current_password, I18n.t('lato.current_password'), class: "form-label" %>
3
- <%= form.password_field :current_password, class: "form-control", autocomplete: "current-password" %>
5
+ <%= form.label :current_password, I18n.t('lato.current_password'), class: "form-label", for: password_id %>
6
+ <%= form.password_field :current_password, id: password_id, class: "form-control", autocomplete: "current-password" %>
4
7
  <% if user.authenticator_enabled? %>
5
- <%= form.label :current_authenticator_code, I18n.t('lato.current_authenticator_code'), class: "form-label mt-2" %>
6
- <%= form.text_field :current_authenticator_code, value: nil, class: "form-control", autocomplete: "one-time-code", inputmode: "numeric" %>
8
+ <%= form.label :current_authenticator_code, I18n.t('lato.current_authenticator_code'), class: "form-label mt-2", for: code_id %>
9
+ <%= form.text_field :current_authenticator_code, id: code_id, value: nil, class: "form-control", autocomplete: "one-time-code", inputmode: "numeric" %>
7
10
  <% elsif user.webauthn_enabled? %>
8
11
  <%= form.hidden_field :current_webauthn_credential, value: nil, data: { lato_account_security_target: "credential" } %>
9
12
  <p class="text-muted mt-2"><%= I18n.t('lato.security_key_confirmation_help') %></p>
@@ -60,7 +60,7 @@
60
60
  </div>
61
61
  <div class="card-body">
62
62
  <div class="alert alert-warning">
63
- <h4 class="alert-heading"><%= I18n.t('lato.warning') %></h4>
63
+ <h3 class="alert-heading h4"><%= I18n.t('lato.warning') %></h3>
64
64
  <p>
65
65
  <%= raw I18n.t('lato.account_delete_details') %>
66
66
  </p>
@@ -102,7 +102,7 @@ default_tab = existing_url ? 'current' : 'draw'
102
102
  <%# Clear button (solo nelle tab di editing) %>
103
103
  <div class="d-flex justify-content-end mt-2" data-lato-input-signature-target="clearRow">
104
104
  <button type="button"
105
- class="btn btn-outline-secondary btn-sm"
105
+ class="btn btn-outline-primary btn-sm"
106
106
  style="font-size: 0.75rem;"
107
107
  data-action="click->lato-input-signature#clearSignature">
108
108
  <i class="bi bi-eraser"></i> <%= I18n.t('lato.signature_clear') %>
@@ -99,7 +99,7 @@ total_pages = collection.empty? ? params["#{key}_page"]&.to_i : 999_999_999 if s
99
99
  <span class="text-muted"><%= collection_total %> <%= I18n.t('lato.total_results').downcase %></span>
100
100
  <% end %>
101
101
  <% if collection.respond_to?(:total_pages) %>
102
- <div class="mt-3"><%= paginate(collection, param_name: "#{key}_page", total_pages: total_pages, window: 1) %></div>
102
+ <div class="mt-3"><%= lato_index_paginate(collection, key, total_pages: total_pages, window: 1) %></div>
103
103
  <% end %>
104
104
  </div>
105
105
 
@@ -161,7 +161,7 @@ total_pages = collection.empty? ? params["#{key}_page"]&.to_i : 999_999_999 if s
161
161
  <td colspan="<%= columns.length %>">
162
162
  <div class="d-flex justify-content-between align-items-center">
163
163
  <% if collection.respond_to?(:total_pages) %>
164
- <div><%= paginate(collection, param_name: "#{key}_page", total_pages: total_pages) %></div>
164
+ <div><%= lato_index_paginate(collection, key, total_pages: total_pages) %></div>
165
165
  <% else %>
166
166
  <div></div>
167
167
  <% end %>
@@ -29,7 +29,7 @@
29
29
 
30
30
  <% if operation.completed_status? %>
31
31
  <div class="text-center">
32
- <div class="fs-1 text-success">
32
+ <div class="fs-1 text-success-emphasis">
33
33
  <i class="bi bi-check-circle"></i>
34
34
  </div>
35
35
  <h2><%= I18n.t('lato.operation_completed_title') %></h2>
@@ -40,7 +40,7 @@
40
40
  <% end %>
41
41
 
42
42
  <% if operation.output_message? %>
43
- <p class="text-success fw-bold">
43
+ <p class="text-success-emphasis fw-bold">
44
44
  <%= operation.output_message %>
45
45
  </p>
46
46
  <% end %>
@@ -5,6 +5,7 @@ en:
5
5
  alert_title: "Error"
6
6
  sort_ascending: "Sort by %{column} ascending"
7
7
  sort_descending: "Sort by %{column} descending"
8
+ pagination_label: "Pagination: %{name}"
8
9
  password_requirements: "At least %{count} characters."
9
10
  too_many_requests: "Too many requests, please wait %{minutes} minutes to retry."
10
11
  hcaptcha_missing: "Please complete the captcha verification."
@@ -7,6 +7,7 @@ fr:
7
7
  alert_title: "Erreur"
8
8
  sort_ascending: "Trier par %{column} croissant"
9
9
  sort_descending: "Trier par %{column} d\u00E9croissant"
10
+ pagination_label: "Pagination : %{name}"
10
11
  password_requirements: "Au moins %{count} caract\u00E8res."
11
12
  too_many_requests: "Trop de requ\u00EAtes, veuillez attendre %{minutes} minutes avant de r\u00E9essayer."
12
13
  hcaptcha_missing: "Veuillez compl\u00E9ter la v\u00E9rification captcha."
@@ -7,6 +7,7 @@ it:
7
7
  alert_title: "Errore"
8
8
  sort_ascending: "Ordina per %{column} crescente"
9
9
  sort_descending: "Ordina per %{column} decrescente"
10
+ pagination_label: "Paginazione: %{name}"
10
11
  password_requirements: "Almeno %{count} caratteri."
11
12
  too_many_requests: "Troppe richieste, attendi %{minutes} minuti prima di riprovare."
12
13
  hcaptcha_missing: "Completa la verifica captcha."
@@ -7,6 +7,7 @@ ro:
7
7
  alert_title: "Eroare"
8
8
  sort_ascending: "Sorteaz\u0103 dup\u0103 %{column} cresc\u0103tor"
9
9
  sort_descending: "Sorteaz\u0103 dup\u0103 %{column} descresc\u0103tor"
10
+ pagination_label: "Paginare: %{name}"
10
11
  password_requirements: "Cel pu\u021Bin %{count} caractere."
11
12
  too_many_requests: "Prea multe cereri, a\u0219tepta\u021Bi %{minutes} minute \u00EEnainte de a re\u00EEncerca."
12
13
  hcaptcha_missing: "Completa\u021Bi verificarea captcha."
data/lib/lato/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Lato
2
- VERSION = "3.26.0"
2
+ VERSION = "3.26.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lato
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.26.0
4
+ version: 3.26.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregorio Galante