fix(quick-260921-bi2): a11y - Beschriftungen an Felder binden (22 Fundstellen)
noLabelWithoutControl (22) auf 0: jede Beschriftung ueber htmlFor/id an ihr Feld gebunden, in Formularen mit wiederholten Zeilen ueber praefixierte, seitenweit eindeutige Kennungen (z.B. ldap-*, user-*, tenant-*). Sonderfall calendar-source-form.tsx: die Farbauswahl beschriftet eine ganze Gruppe von Farb-Schaltflaechen, kein einzelnes Feld. Dafuer fieldset/legend statt htmlFor/id (Rand/Abstand zurueckgesetzt, damit sich am Erscheinungsbild nichts aendert) -- eine Umwandlung in <span> haette die Assoziation entfernt statt sie herzustellen, darum nicht gewaehlt. Damit steht der gesamte Lint-Rueckstand bei 465 (386 echt, 79 Test), Fehlerstufe 0 -- Zielwert dieses Vorgangs erreicht. Die fuenf zurueck- gestellten Regeln (noNoninteractiveElementInteractions, useKeyWithClick- Events, noStaticElementInteractions, useAriaPropsSupportedByRole, noAutofocus) stehen unveraendert bei 11/5/5/5/4. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
@@ -83,8 +83,9 @@ export function GroupFormModal({ group, onClose, onSaved }: GroupFormModalProps)
|
|||||||
</h2>
|
</h2>
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">{t('name')}</label>
|
<label htmlFor="group-name" className="text-sm font-medium text-foreground">{t('name')}</label>
|
||||||
<input
|
<input
|
||||||
|
id="group-name"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
disabled={isImported}
|
disabled={isImported}
|
||||||
@@ -113,10 +114,11 @@ export function GroupFormModal({ group, onClose, onSaved }: GroupFormModalProps)
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label htmlFor="group-internal-name" className="text-sm font-medium text-foreground">
|
||||||
{t('internalName')}
|
{t('internalName')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="group-internal-name"
|
||||||
type="text"
|
type="text"
|
||||||
value={internalName}
|
value={internalName}
|
||||||
onChange={(e) => setInternalName(e.target.value)}
|
onChange={(e) => setInternalName(e.target.value)}
|
||||||
|
|||||||
@@ -557,10 +557,11 @@ export default function AdminLdapPage() {
|
|||||||
<form onSubmit={handleSave} className="space-y-4">
|
<form onSubmit={handleSave} className="space-y-4">
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label htmlFor="ldap-server-url" className="text-sm font-medium text-foreground">
|
||||||
{t('serverUrl')}
|
{t('serverUrl')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="ldap-server-url"
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.serverUrl}
|
value={formData.serverUrl}
|
||||||
onChange={(e) => setFormData({ ...formData, serverUrl: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, serverUrl: e.target.value })}
|
||||||
@@ -570,10 +571,11 @@ export default function AdminLdapPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label htmlFor="ldap-base-dn" className="text-sm font-medium text-foreground">
|
||||||
{t('baseDn')}
|
{t('baseDn')}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
|
id="ldap-base-dn"
|
||||||
value={formData.baseDn}
|
value={formData.baseDn}
|
||||||
onChange={(e) => setFormData({ ...formData, baseDn: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, baseDn: e.target.value })}
|
||||||
placeholder={'dc=example,dc=com\nou=extern,dc=example,dc=com'}
|
placeholder={'dc=example,dc=com\nou=extern,dc=example,dc=com'}
|
||||||
@@ -584,10 +586,11 @@ export default function AdminLdapPage() {
|
|||||||
<p className="text-xs text-muted-foreground">{t('baseDnHint')}</p>
|
<p className="text-xs text-muted-foreground">{t('baseDnHint')}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label htmlFor="ldap-bind-dn" className="text-sm font-medium text-foreground">
|
||||||
{t('bindDn')}
|
{t('bindDn')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="ldap-bind-dn"
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.bindDn}
|
value={formData.bindDn}
|
||||||
onChange={(e) => setFormData({ ...formData, bindDn: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, bindDn: e.target.value })}
|
||||||
@@ -596,10 +599,11 @@ export default function AdminLdapPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label htmlFor="ldap-bind-password" className="text-sm font-medium text-foreground">
|
||||||
{t('bindPassword')}
|
{t('bindPassword')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="ldap-bind-password"
|
||||||
type="password"
|
type="password"
|
||||||
value={formData.bindPassword}
|
value={formData.bindPassword}
|
||||||
onChange={(e) => setFormData({ ...formData, bindPassword: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, bindPassword: e.target.value })}
|
||||||
@@ -609,10 +613,11 @@ export default function AdminLdapPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label htmlFor="ldap-search-filter" className="text-sm font-medium text-foreground">
|
||||||
{t('searchFilter')}
|
{t('searchFilter')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="ldap-search-filter"
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.searchFilter}
|
value={formData.searchFilter}
|
||||||
onChange={(e) => setFormData({ ...formData, searchFilter: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, searchFilter: e.target.value })}
|
||||||
@@ -753,10 +758,11 @@ export default function AdminLdapPage() {
|
|||||||
{showMappingForm && (
|
{showMappingForm && (
|
||||||
<form onSubmit={handleAddMapping} className="mt-4 flex items-end gap-3">
|
<form onSubmit={handleAddMapping} className="mt-4 flex items-end gap-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-xs font-medium text-muted-foreground">
|
<label htmlFor="ldap-mapping-ldap-field" className="text-xs font-medium text-muted-foreground">
|
||||||
{t('fieldMapping.ldapField')}
|
{t('fieldMapping.ldapField')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="ldap-mapping-ldap-field"
|
||||||
type="text"
|
type="text"
|
||||||
value={newMapping.ldapField}
|
value={newMapping.ldapField}
|
||||||
onChange={(e) => setNewMapping({ ...newMapping, ldapField: e.target.value })}
|
onChange={(e) => setNewMapping({ ...newMapping, ldapField: e.target.value })}
|
||||||
@@ -765,10 +771,11 @@ export default function AdminLdapPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-xs font-medium text-muted-foreground">
|
<label htmlFor="ldap-mapping-tessera-field" className="text-xs font-medium text-muted-foreground">
|
||||||
{t('fieldMapping.tesseraField')}
|
{t('fieldMapping.tesseraField')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="ldap-mapping-tessera-field"
|
||||||
type="text"
|
type="text"
|
||||||
value={newMapping.tesseraField}
|
value={newMapping.tesseraField}
|
||||||
onChange={(e) => setNewMapping({ ...newMapping, tesseraField: e.target.value })}
|
onChange={(e) => setNewMapping({ ...newMapping, tesseraField: e.target.value })}
|
||||||
@@ -860,10 +867,11 @@ export default function AdminLdapPage() {
|
|||||||
|
|
||||||
<div className="flex items-end gap-3 mb-4">
|
<div className="flex items-end gap-3 mb-4">
|
||||||
<div className="flex-1 space-y-1">
|
<div className="flex-1 space-y-1">
|
||||||
<label className="text-xs font-medium text-muted-foreground">
|
<label htmlFor="ldap-group-manual-dn" className="text-xs font-medium text-muted-foreground">
|
||||||
{t('groupFilter.manualDn')}
|
{t('groupFilter.manualDn')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="ldap-group-manual-dn"
|
||||||
type="text"
|
type="text"
|
||||||
value={manualDn}
|
value={manualDn}
|
||||||
onChange={(e) => setManualDn(e.target.value)}
|
onChange={(e) => setManualDn(e.target.value)}
|
||||||
@@ -1168,10 +1176,11 @@ export default function AdminLdapPage() {
|
|||||||
|
|
||||||
<div className="flex items-end gap-3 mb-4">
|
<div className="flex items-end gap-3 mb-4">
|
||||||
<div className="flex-1 space-y-1">
|
<div className="flex-1 space-y-1">
|
||||||
<label className="text-xs font-medium text-muted-foreground">
|
<label htmlFor="ldap-exclude-username" className="text-xs font-medium text-muted-foreground">
|
||||||
{t('userExclude.username')}
|
{t('userExclude.username')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="ldap-exclude-username"
|
||||||
type="text"
|
type="text"
|
||||||
value={newExcludeUser}
|
value={newExcludeUser}
|
||||||
onChange={(e) => setNewExcludeUser(e.target.value)}
|
onChange={(e) => setNewExcludeUser(e.target.value)}
|
||||||
@@ -1243,11 +1252,12 @@ export default function AdminLdapPage() {
|
|||||||
{/* Sync interval */}
|
{/* Sync interval */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label htmlFor="ldap-sync-interval" className="text-sm font-medium text-foreground">
|
||||||
{t('sync.interval')}
|
{t('sync.interval')}
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
|
id="ldap-sync-interval"
|
||||||
type="number"
|
type="number"
|
||||||
min={0}
|
min={0}
|
||||||
value={formData.syncIntervalMin}
|
value={formData.syncIntervalMin}
|
||||||
|
|||||||
@@ -250,10 +250,11 @@ export default function AdminTenantsPage() {
|
|||||||
</h2>
|
</h2>
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label htmlFor="tenant-name" className="text-sm font-medium text-foreground">
|
||||||
{t('name')}
|
{t('name')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="tenant-name"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
@@ -265,10 +266,11 @@ export default function AdminTenantsPage() {
|
|||||||
</div>
|
</div>
|
||||||
{!editingTenant && (
|
{!editingTenant && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label htmlFor="tenant-slug" className="text-sm font-medium text-foreground">
|
||||||
{t('slug')}
|
{t('slug')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="tenant-slug"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
pattern="^[a-z0-9-]+$"
|
pattern="^[a-z0-9-]+$"
|
||||||
|
|||||||
@@ -355,10 +355,11 @@ export default function AdminUsersPage() {
|
|||||||
</h2>
|
</h2>
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label htmlFor="user-username" className="text-sm font-medium text-foreground">
|
||||||
{t('username')}
|
{t('username')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="user-username"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
value={formData.username}
|
value={formData.username}
|
||||||
@@ -369,10 +370,11 @@ export default function AdminUsersPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label htmlFor="user-email" className="text-sm font-medium text-foreground">
|
||||||
{t('email')}
|
{t('email')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="user-email"
|
||||||
type="email"
|
type="email"
|
||||||
required
|
required
|
||||||
value={formData.email}
|
value={formData.email}
|
||||||
@@ -383,7 +385,7 @@ export default function AdminUsersPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label htmlFor="user-password" className="text-sm font-medium text-foreground">
|
||||||
{t('password')}
|
{t('password')}
|
||||||
{editingUser && (
|
{editingUser && (
|
||||||
<span className="ml-1 text-xs text-muted-foreground">
|
<span className="ml-1 text-xs text-muted-foreground">
|
||||||
@@ -392,6 +394,7 @@ export default function AdminUsersPage() {
|
|||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="user-password"
|
||||||
type="password"
|
type="password"
|
||||||
required={!editingUser}
|
required={!editingUser}
|
||||||
minLength={8}
|
minLength={8}
|
||||||
@@ -403,10 +406,11 @@ export default function AdminUsersPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label htmlFor="user-display-name" className="text-sm font-medium text-foreground">
|
||||||
{t('displayName')}
|
{t('displayName')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="user-display-name"
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.displayName}
|
value={formData.displayName}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@@ -416,10 +420,11 @@ export default function AdminUsersPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label htmlFor="user-role" className="text-sm font-medium text-foreground">
|
||||||
{t('role')}
|
{t('role')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
|
id="user-role"
|
||||||
value={formData.role}
|
value={formData.role}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({
|
setFormData({
|
||||||
|
|||||||
@@ -38,13 +38,14 @@ export function TenantContextSelector() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-muted/50 rounded-lg p-3 mb-4 border border-border">
|
<div className="bg-muted/50 rounded-lg p-3 mb-4 border border-border">
|
||||||
<label className="block text-xs font-semibold text-muted-foreground mb-1.5">
|
<label htmlFor="tenant-context-select" className="block text-xs font-semibold text-muted-foreground mb-1.5">
|
||||||
{t('tenantContextLabel')}
|
{t('tenantContextLabel')}
|
||||||
</label>
|
</label>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<span className="text-sm text-muted-foreground">Laden...</span>
|
<span className="text-sm text-muted-foreground">Laden...</span>
|
||||||
) : (
|
) : (
|
||||||
<select
|
<select
|
||||||
|
id="tenant-context-select"
|
||||||
onChange={(e) => setSelectedTenantId(e.target.value || null)}
|
onChange={(e) => setSelectedTenantId(e.target.value || null)}
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
className="bg-card border border-border rounded-md px-3 py-1.5 text-sm min-w-[200px]"
|
className="bg-card border border-border rounded-md px-3 py-1.5 text-sm min-w-[200px]"
|
||||||
|
|||||||
@@ -18,11 +18,12 @@ export function PasswordField({ value, onChange, show }: PasswordFieldProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="block text-sm text-foreground">
|
<label htmlFor="cert-manager-password" className="block text-sm text-foreground">
|
||||||
{t('password.label')}
|
{t('password.label')}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative flex items-center">
|
<div className="relative flex items-center">
|
||||||
<input
|
<input
|
||||||
|
id="cert-manager-password"
|
||||||
type={revealed ? 'text' : 'password'}
|
type={revealed ? 'text' : 'password'}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onChange(e.target.value)}
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
|||||||
@@ -324,9 +324,13 @@ export function CalendarSourceForm({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Color */}
|
{/* Color -- a11y/noLabelWithoutControl: kein htmlFor/id, weil dieses
|
||||||
<div>
|
Label eine ganze Gruppe von Farb-Schaltflaechen beschriftet, kein
|
||||||
<label className="mb-1 block text-sm text-foreground">{t('calendar.formFieldColor')}</label>
|
einzelnes Feld. fieldset/legend ist die dafuer vorgesehene Semantik
|
||||||
|
(Rand/Abstand zurueckgesetzt, damit sich am Erscheinungsbild nichts
|
||||||
|
aendert). */}
|
||||||
|
<fieldset className="m-0 border-0 p-0">
|
||||||
|
<legend className="mb-1 block text-sm text-foreground">{t('calendar.formFieldColor')}</legend>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{SOURCE_COLOR_PALETTE.map((c) => (
|
{SOURCE_COLOR_PALETTE.map((c) => (
|
||||||
<button
|
<button
|
||||||
@@ -343,7 +347,7 @@ export function CalendarSourceForm({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</fieldset>
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex flex-wrap gap-3 pt-2">
|
<div className="flex flex-wrap gap-3 pt-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user