feat(a11y): fix SortDropdown accessibility — label, aria-label i18n, chevron

- Add sr-only <label> for the sort <select> (WCAG 1.3.1)
- Replace hardcoded German aria-label with Paraglide sort_dir_asc/desc keys
- Add custom SVG chevron overlay to restore visual dropdown indicator
  (appearance-none had removed the native browser arrow)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-06 16:49:06 +02:00
parent 56f7282a9d
commit c82bd61ad4
4 changed files with 46 additions and 24 deletions

View File

@@ -60,7 +60,7 @@
"docs_sort_receiver": "Empfänger",
"docs_sort_upload": "Hochgeladen",
"docs_result_count": "{count} Dokumente",
"docs_empty_for_term": "Keine Dokumente f\u00fcr \"{term}\" gefunden",
"docs_empty_for_term": "Keine Dokumente für \"{term}\" gefunden",
"docs_btn_filter": "Filter",
"docs_btn_reset_title": "Filter zurücksetzen",
"docs_filter_label_tags": "Schlagworte",
@@ -182,7 +182,7 @@
"admin_tags_warning": "Warnung: Umbenennen oder Löschen wirkt sich auf alle verknüpften Dokumente aus.",
"admin_tags_list_title": "Alle Schlagworte",
"admin_tags_empty": "Keine Schlagworte vorhanden.",
"admin_tags_select_prompt": "W\u00e4hle ein Schlagwort aus der Liste.",
"admin_tags_select_prompt": "Wähle ein Schlagwort aus der Liste.",
"admin_tag_edit_heading": "Schlagwort: {name}",
"admin_tag_updated": "Schlagwort umbenannt.",
"admin_unsaved_warning": "Du hast ungespeicherte Änderungen speichere oder verwerfe, bevor du wechselst.",
@@ -201,13 +201,13 @@
"admin_user_delete_confirm": "Benutzer {username} wirklich löschen?",
"admin_btn_new_user": "Neuer Benutzer",
"admin_users_list_title": "Alle Benutzer",
"admin_users_search_placeholder": "Benutzer suchen\u2026",
"admin_users_search_placeholder": "Benutzer suchen",
"admin_users_empty": "Keine Benutzer vorhanden.",
"admin_users_select_prompt": "W\u00e4hle einen Benutzer aus der Liste.",
"admin_users_select_prompt": "Wähle einen Benutzer aus der Liste.",
"admin_btn_new_group": "Neue Gruppe",
"admin_groups_list_title": "Alle Gruppen",
"admin_groups_empty": "Keine Gruppen vorhanden.",
"admin_groups_select_prompt": "W\u00e4hle eine Gruppe aus der Liste.",
"admin_groups_select_prompt": "Wähle eine Gruppe aus der Liste.",
"admin_groups_permission_count": "{count} Berechtigungen",
"admin_group_new_heading": "Neue Gruppe anlegen",
"admin_group_edit_heading": "Gruppe: {name}",
@@ -433,7 +433,7 @@
"notification_load_more": "Ältere laden",
"notification_empty_history": "Keine Benachrichtigungen",
"notification_empty_history_body": "Hier erscheinen Erwähnungen und Antworten auf deine Kommentare.",
"notification_row_aria": "{actor} {type} auf \u201e{title}\u201c \u2014 {time} \u2014 {readState}",
"notification_row_aria": "{actor} {type} auf {title}“ — {time} {readState}",
"notification_read_state_read": "gelesen",
"notification_read_state_unread": "ungelesen",
"error_transcription_block_not_found": "Der Transkriptionsblock wurde nicht gefunden.",
@@ -464,5 +464,7 @@
"transcription_next_block_cta": "Markiere eine weitere Passage im Scan, um Block {number} anzulegen",
"transcription_draw_tooltip": "Klicken und ziehen, um einen Textbereich zu markieren",
"transcription_quote_stale": "Zitat aus älterer Version",
"transcription_block_conflict": "Dieser Block wurde von jemand anderem geändert — bitte neu laden"
"transcription_block_conflict": "Dieser Block wurde von jemand anderem geändert — bitte neu laden",
"sort_dir_asc": "Aufsteigend sortieren",
"sort_dir_desc": "Absteigend sortieren"
}

View File

@@ -201,7 +201,7 @@
"admin_user_delete_confirm": "Really delete user {username}?",
"admin_btn_new_user": "New User",
"admin_users_list_title": "All Users",
"admin_users_search_placeholder": "Search users\u2026",
"admin_users_search_placeholder": "Search users",
"admin_users_empty": "No users found.",
"admin_users_select_prompt": "Select a user from the list.",
"admin_btn_new_group": "New Group",
@@ -464,5 +464,7 @@
"transcription_next_block_cta": "Mark another passage on the scan to create block {number}",
"transcription_draw_tooltip": "Click and drag to mark a text region",
"transcription_quote_stale": "Quote from an older version",
"transcription_block_conflict": "This block was changed by someone else — please reload"
"transcription_block_conflict": "This block was changed by someone else — please reload",
"sort_dir_asc": "Sort ascending",
"sort_dir_desc": "Sort descending"
}

View File

@@ -201,7 +201,7 @@
"admin_user_delete_confirm": "¿Realmente eliminar al usuario {username}?",
"admin_btn_new_user": "Nuevo usuario",
"admin_users_list_title": "Todos los usuarios",
"admin_users_search_placeholder": "Buscar usuarios\u2026",
"admin_users_search_placeholder": "Buscar usuarios",
"admin_users_empty": "No hay usuarios.",
"admin_users_select_prompt": "Selecciona un usuario de la lista.",
"admin_btn_new_group": "Nuevo grupo",
@@ -213,7 +213,7 @@
"admin_group_edit_heading": "Grupo: {name}",
"admin_group_updated": "Grupo guardado.",
"admin_group_created": "Grupo creado.",
"admin_groups_section_standard": "Est\u00e1ndar",
"admin_groups_section_standard": "Estándar",
"admin_groups_section_administrative": "Administrativo",
"admin_perm_read_all": "Solo lectura",
"admin_perm_annotate_all": "Leer y anotar",
@@ -464,5 +464,7 @@
"transcription_next_block_cta": "Marque otro pasaje en el escaneo para crear el bloque {number}",
"transcription_draw_tooltip": "Haga clic y arrastre para marcar una región de texto",
"transcription_quote_stale": "Cita de una versión anterior",
"transcription_block_conflict": "Este bloque fue cambiado por otra persona — por favor recargue"
"transcription_block_conflict": "Este bloque fue cambiado por otra persona — por favor recargue",
"sort_dir_asc": "Ordenar ascendente",
"sort_dir_desc": "Ordenar descendente"
}

View File

@@ -14,22 +14,38 @@ function toggleDir() {
</script>
<div class="inline-flex items-stretch">
<select
role="combobox"
bind:value={sort}
class="appearance-none border border-line bg-muted px-4 py-2.5 text-sm font-bold text-ink-2 transition hover:text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
>
<option value="DATE">{m.docs_sort_date()}</option>
<option value="TITLE">{m.docs_sort_title()}</option>
<option value="SENDER">{m.docs_sort_sender()}</option>
<option value="RECEIVER">{m.docs_sort_receiver()}</option>
<option value="UPLOAD_DATE">{m.docs_sort_upload()}</option>
</select>
<label for="sort-field" class="sr-only">{m.docs_sort_label()}</label>
<div class="relative">
<select
id="sort-field"
bind:value={sort}
class="appearance-none border border-line bg-muted py-2.5 pr-9 pl-4 text-sm font-bold text-ink-2 transition hover:text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
>
<option value="DATE">{m.docs_sort_date()}</option>
<option value="TITLE">{m.docs_sort_title()}</option>
<option value="SENDER">{m.docs_sort_sender()}</option>
<option value="RECEIVER">{m.docs_sort_receiver()}</option>
<option value="UPLOAD_DATE">{m.docs_sort_upload()}</option>
</select>
<svg
class="pointer-events-none absolute top-1/2 right-2.5 h-4 w-4 -translate-y-1/2 text-ink-2"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
</svg>
</div>
<button
type="button"
onclick={toggleDir}
class="-ml-px flex items-center justify-center border border-line bg-muted px-3 py-2.5 text-sm font-bold text-ink-2 transition hover:text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
aria-label={dir === 'asc' ? 'Aufsteigend sortieren' : 'Absteigend sortieren'}
aria-label={dir === 'asc' ? m.sort_dir_asc() : m.sort_dir_desc()}
>
{dir === 'asc' ? '↑' : '↓'}
</button>