Enhance accessibility and improve form semantics across multiple pages
Build & Push Docker Image / build (push) Successful in 4m4s
Build & Push Docker Image / build (push) Successful in 4m4s
- Added `htmlFor` attributes to labels for better accessibility in Calendar, Dashboard, GuestJoin, Login, Register, RoomDetail, and Settings pages. - Included `aria-hidden` attributes for icons to improve screen reader experience. - Set `autoComplete` attributes for input fields to enhance user experience during form filling. - Implemented `role` and `aria` attributes for radio groups and buttons to improve accessibility compliance.
This commit is contained in:
+40
-19
@@ -301,7 +301,7 @@ export default function Settings() {
|
||||
{user?.avatar_image ? (
|
||||
<img
|
||||
src={`${api.defaults.baseURL}/auth/avatar/${user.avatar_image}`}
|
||||
alt="Avatar"
|
||||
alt=""
|
||||
className="w-16 h-16 rounded-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
@@ -316,6 +316,7 @@ export default function Settings() {
|
||||
type="button"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={uploadingAvatar}
|
||||
aria-label={t('settings.uploadImage')}
|
||||
className="absolute inset-0 rounded-full bg-black/50 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer"
|
||||
>
|
||||
{uploadingAvatar ? (
|
||||
@@ -360,12 +361,15 @@ export default function Settings() {
|
||||
|
||||
{/* Avatar Color (fallback) */}
|
||||
<div className="mb-6">
|
||||
<label className="block text-sm font-medium text-th-text mb-3">{t('settings.avatarColor')}</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<p id="avatar-color-label" className="block text-sm font-medium text-th-text mb-3">{t('settings.avatarColor')}</p>
|
||||
<div role="radiogroup" aria-labelledby="avatar-color-label" className="flex flex-wrap gap-2">
|
||||
{avatarColors.map(color => (
|
||||
<button
|
||||
key={color}
|
||||
onClick={() => handleAvatarColor(color)}
|
||||
role="radio"
|
||||
aria-checked={user?.avatar_color === color}
|
||||
aria-label={color}
|
||||
className={`w-7 h-7 rounded-full ring-2 ring-offset-2 transition-all ${
|
||||
user?.avatar_color === color ? 'ring-th-accent' : 'ring-transparent hover:ring-th-border'
|
||||
}`}
|
||||
@@ -378,11 +382,13 @@ export default function Settings() {
|
||||
|
||||
<form onSubmit={handleProfileSave} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">{t('auth.username')}</label>
|
||||
<label htmlFor="settings-username" className="block text-sm font-medium text-th-text mb-1.5">{t('auth.username')}</label>
|
||||
<div className="relative">
|
||||
<User size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
|
||||
<User size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" aria-hidden="true" />
|
||||
<input
|
||||
id="settings-username"
|
||||
type="text"
|
||||
autoComplete="username"
|
||||
value={profile.name}
|
||||
onChange={e => setProfile({ ...profile, name: e.target.value })}
|
||||
className="input-field pl-11"
|
||||
@@ -392,11 +398,13 @@ export default function Settings() {
|
||||
<p className="text-xs text-th-text-s mt-1">{t('auth.usernameHint')}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">{t('auth.displayName')}</label>
|
||||
<label htmlFor="settings-display-name" className="block text-sm font-medium text-th-text mb-1.5">{t('auth.displayName')}</label>
|
||||
<div className="relative">
|
||||
<User size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
|
||||
<User size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" aria-hidden="true" />
|
||||
<input
|
||||
id="settings-display-name"
|
||||
type="text"
|
||||
autoComplete="name"
|
||||
value={profile.display_name}
|
||||
onChange={e => setProfile({ ...profile, display_name: e.target.value })}
|
||||
className="input-field pl-11"
|
||||
@@ -404,11 +412,13 @@ export default function Settings() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">{t('auth.email')}</label>
|
||||
<label htmlFor="settings-email" className="block text-sm font-medium text-th-text mb-1.5">{t('auth.email')}</label>
|
||||
<div className="relative">
|
||||
<Mail size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
|
||||
<Mail size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" aria-hidden="true" />
|
||||
<input
|
||||
id="settings-email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
value={profile.email}
|
||||
onChange={e => setProfile({ ...profile, email: e.target.value })}
|
||||
className="input-field pl-11"
|
||||
@@ -430,11 +440,13 @@ export default function Settings() {
|
||||
<h2 className="text-lg font-semibold text-th-text mb-6">{t('settings.changePassword')}</h2>
|
||||
<form onSubmit={handlePasswordSave} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">{t('settings.currentPassword')}</label>
|
||||
<label htmlFor="settings-current-password" className="block text-sm font-medium text-th-text mb-1.5">{t('settings.currentPassword')}</label>
|
||||
<div className="relative">
|
||||
<Lock size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
|
||||
<Lock size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" aria-hidden="true" />
|
||||
<input
|
||||
id="settings-current-password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
value={passwords.currentPassword}
|
||||
onChange={e => setPasswords({ ...passwords, currentPassword: e.target.value })}
|
||||
className="input-field pl-11"
|
||||
@@ -443,11 +455,13 @@ export default function Settings() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">{t('settings.newPassword')}</label>
|
||||
<label htmlFor="settings-new-password" className="block text-sm font-medium text-th-text mb-1.5">{t('settings.newPassword')}</label>
|
||||
<div className="relative">
|
||||
<Lock size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
|
||||
<Lock size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" aria-hidden="true" />
|
||||
<input
|
||||
id="settings-new-password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
value={passwords.newPassword}
|
||||
onChange={e => setPasswords({ ...passwords, newPassword: e.target.value })}
|
||||
className="input-field pl-11"
|
||||
@@ -458,11 +472,13 @@ export default function Settings() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">{t('settings.confirmNewPassword')}</label>
|
||||
<label htmlFor="settings-confirm-new-password" className="block text-sm font-medium text-th-text mb-1.5">{t('settings.confirmNewPassword')}</label>
|
||||
<div className="relative">
|
||||
<Lock size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
|
||||
<Lock size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" aria-hidden="true" />
|
||||
<input
|
||||
id="settings-confirm-new-password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
value={passwords.confirmPassword}
|
||||
onChange={e => setPasswords({ ...passwords, confirmPassword: e.target.value })}
|
||||
className="input-field pl-11"
|
||||
@@ -514,11 +530,13 @@ export default function Settings() {
|
||||
<form onSubmit={handleDisable2FA} className="space-y-4 p-4 rounded-xl bg-th-bg-t border border-th-border">
|
||||
<p className="text-sm text-th-text-s">{t('settings.security.disableConfirm')}</p>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">{t('settings.currentPassword')}</label>
|
||||
<label htmlFor="twofa-disable-password" className="block text-sm font-medium text-th-text mb-1.5">{t('settings.currentPassword')}</label>
|
||||
<div className="relative">
|
||||
<Lock size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
|
||||
<Lock size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" aria-hidden="true" />
|
||||
<input
|
||||
id="twofa-disable-password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
value={twoFaDisablePassword}
|
||||
onChange={e => setTwoFaDisablePassword(e.target.value)}
|
||||
className="input-field pl-11"
|
||||
@@ -527,8 +545,9 @@ export default function Settings() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">{t('settings.security.codeLabel')}</label>
|
||||
<label htmlFor="twofa-disable-code" className="block text-sm font-medium text-th-text mb-1.5">{t('settings.security.codeLabel')}</label>
|
||||
<input
|
||||
id="twofa-disable-code"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
autoComplete="one-time-code"
|
||||
@@ -570,6 +589,7 @@ export default function Settings() {
|
||||
<button
|
||||
onClick={() => { navigator.clipboard.writeText(twoFaSetupData.secret); toast.success(t('room.linkCopied')); }}
|
||||
className="btn-ghost py-1.5 px-2 shrink-0"
|
||||
aria-label={t('room.copyLink')}
|
||||
>
|
||||
<Copy size={14} />
|
||||
</button>
|
||||
@@ -577,8 +597,9 @@ export default function Settings() {
|
||||
</div>
|
||||
<form onSubmit={handleEnable2FA} className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">{t('settings.security.verifyCode')}</label>
|
||||
<label htmlFor="twofa-enable-code" className="block text-sm font-medium text-th-text mb-1.5">{t('settings.security.verifyCode')}</label>
|
||||
<input
|
||||
id="twofa-enable-code"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
autoComplete="one-time-code"
|
||||
|
||||
Reference in New Issue
Block a user