Skip to content

Commit 871ba0d

Browse files
change label to p to prevent label click-through onto input (#145)
* change label to p to prevent label click-through onto input * add useId to label ids, fix labels in EditAbsenceForm * Improve accessibility for teacher selection fields Added unique aria-labelledby attributes and label IDs to the AdminTeacherFields and SearchDropdown components to enhance accessibility. Also fixed minor formatting in DeclareAbsenceForm. These changes ensure better screen reader support for form fields. --------- Co-authored-by: Chinemerem <chinemeremchigbo@Outlook.com>
1 parent 37dcc62 commit 871ba0d

File tree

6 files changed

+54
-16
lines changed

6 files changed

+54
-16
lines changed

src/components/absences/modals/AdminTeacherFields.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
FormLabel,
55
Text,
66
} from '@chakra-ui/react';
7+
import { useId } from 'react';
78
import { SearchDropdown } from './SearchDropdown';
89

910
interface AdminTeacherFieldsProps {
@@ -28,14 +29,21 @@ export const AdminTeacherFields: React.FC<AdminTeacherFieldsProps> = ({
2829
setFormData,
2930
setErrors,
3031
}) => {
32+
const id = useId();
33+
3134
return (
3235
<>
3336
<FormControl isRequired isInvalid={!!errors.absentTeacherId}>
34-
<FormLabel htmlFor="absentTeacher" sx={{ display: 'flex' }}>
37+
<FormLabel
38+
id={'absentTeacherLabel' + id}
39+
as="p"
40+
sx={{ display: 'flex' }}
41+
>
3542
<Text textStyle="h4">Teacher Absent</Text>
3643
</FormLabel>
3744
<SearchDropdown
3845
id="absentTeacher"
46+
ariaLabelledBy={'absentTeacherLabel' + id}
3947
excludedId={formData.substituteTeacherId}
4048
defaultValueId={Number(formData.absentTeacherId)}
4149
onChange={(value) => {
@@ -55,11 +63,16 @@ export const AdminTeacherFields: React.FC<AdminTeacherFieldsProps> = ({
5563
</FormControl>
5664

5765
<FormControl>
58-
<FormLabel htmlFor="substituteTeacher" sx={{ display: 'flex' }}>
66+
<FormLabel
67+
id={'substituteTeacherLabel' + id}
68+
as="p"
69+
sx={{ display: 'flex' }}
70+
>
5971
<Text textStyle="h4">Substitute Teacher</Text>
6072
</FormLabel>
6173
<SearchDropdown
6274
id="substituteTeacher"
75+
ariaLabelledBy={'substituteTeacherLabel' + id}
6376
excludedId={formData.absentTeacherId}
6477
defaultValueId={
6578
formData.substituteTeacherId

src/components/absences/modals/DateOfAbsence.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
PopoverTrigger,
1010
Text,
1111
} from '@chakra-ui/react';
12-
import { useCallback, useEffect, useState } from 'react';
12+
import { useCallback, useEffect, useState, useId } from 'react';
1313
import MiniCalendar from '../../calendar/MiniCalendar';
1414

1515
interface DateOfAbsenceProps {
@@ -66,9 +66,11 @@ export const DateOfAbsence: React.FC<DateOfAbsenceProps> = ({
6666
[onDateSelect]
6767
);
6868

69+
const id = useId();
70+
6971
return (
7072
<FormControl isRequired isInvalid={!!error}>
71-
<FormLabel sx={{ display: 'flex' }}>
73+
<FormLabel as="p" id={'dateOfAbsenceLabel' + id} sx={{ display: 'flex' }}>
7274
<Text textStyle="h4">{label}</Text>
7375
</FormLabel>
7476
<Popover
@@ -80,6 +82,7 @@ export const DateOfAbsence: React.FC<DateOfAbsenceProps> = ({
8082
<PopoverTrigger>
8183
<Box>
8284
<Input
85+
aria-labelledby={'dateOfAbsenceLabel' + id}
8386
value={inputValue}
8487
onChange={handleInputChange}
8588
placeholder="YYYY-MM-DD"

src/components/absences/modals/InputDropdown.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ import { useCustomToast } from '../../CustomToast';
1818
export type Option = { name: string; id: number };
1919

2020
interface InputDropdownProps {
21+
ariaLabelledBy: string;
2122
label: string;
2223
type: 'location' | 'subject';
2324
onChange: (value: Option | null) => void;
2425
defaultValueId?: number;
2526
}
2627

2728
export const InputDropdown: React.FC<InputDropdownProps> = ({
29+
ariaLabelledBy,
2830
label,
2931
type,
3032
onChange,
@@ -120,6 +122,7 @@ export const InputDropdown: React.FC<InputDropdownProps> = ({
120122
value={selectedOption ? selectedOption.name : ''}
121123
readOnly
122124
_focusVisible={{ outline: 'none' }}
125+
aria-labelledby={ariaLabelledBy}
123126
/>
124127
<InputRightElement pointerEvents="none">
125128
{isOpen ? <IoChevronUp /> : <IoChevronDown />}

src/components/absences/modals/SearchDropdown.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ export type Option = { name: string; id: number; profilePicture: string };
2121

2222
interface SearchDropdownProps {
2323
id: string;
24+
ariaLabelledBy: string;
2425
excludedId?: string;
2526
defaultValueId?: number;
2627
onChange: (value: Option | null) => void;
2728
}
2829

2930
export const SearchDropdown: React.FC<SearchDropdownProps> = ({
3031
id,
32+
ariaLabelledBy,
3133
excludedId,
3234
defaultValueId,
3335
onChange,
@@ -174,6 +176,7 @@ export const SearchDropdown: React.FC<SearchDropdownProps> = ({
174176
id={id}
175177
name={id}
176178
ref={inputRef}
179+
aria-labelledby={ariaLabelledBy}
177180
value={searchQuery}
178181
onChange={isSelected ? undefined : handleSearchChange}
179182
onClick={() => {

src/components/absences/modals/declare/DeclareAbsenceForm.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { Absence, Prisma } from '@prisma/client';
1414
import { formatFullDate } from '@utils/dates';
1515
import { submitAbsence } from '@utils/submitAbsence';
1616
import { validateAbsenceForm } from '@utils/validateAbsenceForm';
17-
import { useState } from 'react';
17+
import { useId, useState } from 'react';
1818
import { useCustomToast } from '../../../CustomToast';
1919
import { FileUpload } from '../../FileUpload';
2020
import { AdminTeacherFields } from '../AdminTeacherFields';
@@ -317,6 +317,8 @@ const DeclareAbsenceForm: React.FC<DeclareAbsenceFormProps> = ({
317317
const isUrgent =
318318
(selectedDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24) <= 7;
319319

320+
const id = useId();
321+
320322
return (
321323
<Box
322324
as="form"
@@ -337,10 +339,11 @@ const DeclareAbsenceForm: React.FC<DeclareAbsenceFormProps> = ({
337339
)}
338340

339341
<FormControl isRequired isInvalid={!!errors.subjectId}>
340-
<FormLabel sx={{ display: 'flex' }}>
342+
<FormLabel id={'subjectLabel' + id} as="p" sx={{ display: 'flex' }}>
341343
<Text textStyle="h4">Subject</Text>
342344
</FormLabel>
343345
<InputDropdown
346+
ariaLabelledBy={'subjectLabel' + id}
344347
label="subject"
345348
type="subject"
346349
onChange={(value) => {
@@ -360,10 +363,11 @@ const DeclareAbsenceForm: React.FC<DeclareAbsenceFormProps> = ({
360363
</FormControl>
361364

362365
<FormControl isRequired isInvalid={!!errors.locationId}>
363-
<FormLabel sx={{ display: 'flex' }}>
366+
<FormLabel id={'locationLabel' + id} as="p" sx={{ display: 'flex' }}>
364367
<Text textStyle="h4">Location</Text>
365368
</FormLabel>
366369
<InputDropdown
370+
ariaLabelledBy={'locationLabel' + id}
367371
label="location"
368372
type="location"
369373
onChange={(value) => {
@@ -381,29 +385,33 @@ const DeclareAbsenceForm: React.FC<DeclareAbsenceFormProps> = ({
381385
/>
382386
<FormErrorMessage>{errors.locationId}</FormErrorMessage>
383387
</FormControl>
388+
384389
<FormControl>
385-
<FormLabel htmlFor="roomNumber" sx={{ display: 'flex' }}>
390+
<FormLabel id={'roomNumberLabel' + id} sx={{ display: 'flex' }}>
386391
<Text textStyle="h4">Room Number</Text>
387392
</FormLabel>
388393
<Input
394+
aria-labelledby={'roomNumberLabel' + id}
389395
id="roomNumber"
390396
name="roomNumber"
391397
placeholder="e.g. 2131"
392398
value={formData.roomNumber}
393399
onChange={handleChange}
394400
/>
395401
</FormControl>
402+
396403
<DateOfAbsence
397404
dateValue={dateValue}
398405
onDateSelect={handleDateSelect}
399406
error={errors.lessonDate}
400407
/>
401408

402409
<FormControl isRequired isInvalid={!!errors.reasonOfAbsence}>
403-
<FormLabel htmlFor="reasonOfAbsence" sx={{ display: 'flex' }}>
410+
<FormLabel id={'reasonOfAbsenceLabel' + id} sx={{ display: 'flex' }}>
404411
<Text textStyle="h4">Reason of Absence</Text>
405412
</FormLabel>
406413
<Textarea
414+
aria-labelledby={'reasonOfAbsenceLabel' + id}
407415
id="reasonOfAbsence"
408416
name="reasonOfAbsence"
409417
placeholder="Only visible to admin"
@@ -422,10 +430,11 @@ const DeclareAbsenceForm: React.FC<DeclareAbsenceFormProps> = ({
422430
</FormControl>
423431

424432
<FormControl>
425-
<FormLabel htmlFor="notes" sx={{ display: 'flex' }}>
433+
<FormLabel id={'notesLabel' + id} sx={{ display: 'flex' }}>
426434
<Text textStyle="h4">Notes</Text>
427435
</FormLabel>
428436
<Textarea
437+
aria-labelledby={'notesLabel' + id}
429438
id="notes"
430439
name="notes"
431440
placeholder="Visible to everyone"

src/components/absences/modals/edit/EditAbsenceForm.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { formatFullDate } from '@utils/dates';
1717
import { submitAbsence } from '@utils/submitAbsence';
1818
import { EventDetails } from '@utils/types';
1919
import { validateAbsenceForm } from '@utils/validateAbsenceForm';
20-
import { useState } from 'react';
20+
import { useState, useId } from 'react';
2121
import { IoMailOutline } from 'react-icons/io5';
2222
import { useCustomToast } from '../../../CustomToast';
2323
import { FileUpload } from '../../FileUpload';
@@ -282,6 +282,8 @@ const EditAbsenceForm: React.FC<EditAbsenceFormProps> = ({
282282
}
283283
};
284284

285+
const id = useId();
286+
285287
return (
286288
<Box
287289
as="form"
@@ -300,10 +302,11 @@ const EditAbsenceForm: React.FC<EditAbsenceFormProps> = ({
300302
)}
301303

302304
<FormControl isRequired isInvalid={!!errors.subjectId}>
303-
<FormLabel sx={{ display: 'flex' }}>
305+
<FormLabel id={'subjectLabel' + id} as="p" sx={{ display: 'flex' }}>
304306
<Text textStyle="h4">Subject</Text>
305307
</FormLabel>
306308
<InputDropdown
309+
ariaLabelledBy={'subjectLabel' + id}
307310
label="subject"
308311
type="subject"
309312
onChange={(value) => {
@@ -324,10 +327,11 @@ const EditAbsenceForm: React.FC<EditAbsenceFormProps> = ({
324327
</FormControl>
325328

326329
<FormControl isRequired isInvalid={!!errors.locationId}>
327-
<FormLabel sx={{ display: 'flex' }}>
330+
<FormLabel id={'locationLabel' + id} as="p" sx={{ display: 'flex' }}>
328331
<Text textStyle="h4">Location</Text>
329332
</FormLabel>
330333
<InputDropdown
334+
ariaLabelledBy={'locationLabel' + id}
331335
label="location"
332336
type="location"
333337
onChange={(value) => {
@@ -348,10 +352,11 @@ const EditAbsenceForm: React.FC<EditAbsenceFormProps> = ({
348352
</FormControl>
349353

350354
<FormControl>
351-
<FormLabel htmlFor="roomNumber" sx={{ display: 'flex' }}>
355+
<FormLabel id={'roomNumberLabel' + id} sx={{ display: 'flex' }}>
352356
<Text textStyle="h4">Room Number</Text>
353357
</FormLabel>
354358
<Input
359+
aria-labelledby={'roomNumberLabel' + id}
355360
id="roomNumber"
356361
name="roomNumber"
357362
placeholder="e.g. 2131"
@@ -367,10 +372,11 @@ const EditAbsenceForm: React.FC<EditAbsenceFormProps> = ({
367372
/>
368373

369374
<FormControl isRequired isInvalid={!!errors.reasonOfAbsence}>
370-
<FormLabel htmlFor="reasonOfAbsence" sx={{ display: 'flex' }}>
375+
<FormLabel id={'reasonOfAbsenceLabel' + id} sx={{ display: 'flex' }}>
371376
<Text textStyle="h4">Reason of Absence</Text>
372377
</FormLabel>
373378
<Textarea
379+
aria-labelledby={'reasonOfAbsenceLabel' + id}
374380
id="reasonOfAbsence"
375381
name="reasonOfAbsence"
376382
placeholder="Only visible to admin"
@@ -393,10 +399,11 @@ const EditAbsenceForm: React.FC<EditAbsenceFormProps> = ({
393399
</FormControl>
394400

395401
<FormControl>
396-
<FormLabel htmlFor="notes" sx={{ display: 'flex' }}>
402+
<FormLabel id={'notesLabel' + id} sx={{ display: 'flex' }}>
397403
<Text textStyle="h4">Notes</Text>
398404
</FormLabel>
399405
<Textarea
406+
aria-labelledby={'notesLabel' + id}
400407
id="notes"
401408
name="notes"
402409
placeholder="Visible to everyone"

0 commit comments

Comments
 (0)