feat(accessibility): improve dose and reminder cards

This commit is contained in:
William Valentin
2025-09-23 10:47:48 -07:00
parent 2cb56d5f5f
commit 35dcae07e5
2 changed files with 56 additions and 4 deletions

View File

@@ -59,6 +59,13 @@ const statusStyles = {
},
};
const statusLabels: Record<DoseStatus, string> = {
[DoseStatus.UPCOMING]: 'Upcoming dose',
[DoseStatus.TAKEN]: 'Dose taken',
[DoseStatus.MISSED]: 'Dose missed',
[DoseStatus.SNOOZED]: 'Dose snoozed',
};
const DoseCard: React.FC<DoseCardProps> = ({
dose,
medication,
@@ -68,6 +75,7 @@ const DoseCard: React.FC<DoseCardProps> = ({
snoozedUntil,
}) => {
const styles = statusStyles[status];
const statusLabel = statusLabels[status];
const timeString = dose.scheduledTime.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
@@ -85,17 +93,48 @@ const DoseCard: React.FC<DoseCardProps> = ({
})
: '';
const MedicationIcon = getMedicationIcon(medication.icon);
const cardTitleId = `dose-${dose.id}-title`;
const statusId = `dose-${dose.id}-status`;
const handleCardKeyDown = (
event: React.KeyboardEvent<HTMLLIElement>
): void => {
if (event.target !== event.currentTarget) {
return;
}
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
onToggleDose(dose.id);
}
if (
(event.key.toLowerCase() === 's' || event.key === 'S') &&
status === DoseStatus.UPCOMING
) {
event.preventDefault();
onSnooze(dose.id);
}
};
return (
<li
className={`shadow-md rounded-lg p-4 flex flex-col justify-between transition-all duration-300 ${styles.bg} ${styles.ring} ring-4 ring-transparent border border-slate-200 dark:border-slate-700`}
role='group'
tabIndex={0}
onKeyDown={handleCardKeyDown}
aria-labelledby={cardTitleId}
aria-describedby={statusId}
className={`shadow-md rounded-lg p-4 flex flex-col justify-between transition-all duration-300 ${styles.bg} ${styles.ring} ring-4 ring-transparent border border-slate-200 dark:border-slate-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-400 dark:focus:ring-offset-slate-900`}
>
<div>
<div className='flex justify-between items-start'>
<div className='flex items-center space-x-3'>
<MedicationIcon className='w-7 h-7 text-indigo-500 dark:text-indigo-400 flex-shrink-0' />
<div>
<h4 className='font-bold text-lg text-slate-800 dark:text-slate-100'>
<h4
id={cardTitleId}
className='font-bold text-lg text-slate-800 dark:text-slate-100'
>
{medication.name}
</h4>
<p className='text-slate-600 dark:text-slate-300'>
@@ -106,9 +145,14 @@ const DoseCard: React.FC<DoseCardProps> = ({
{styles.icon}
</div>
<div
id={statusId}
role='status'
aria-live='polite'
aria-atomic='true'
className={`flex items-center space-x-2 mt-4 font-semibold text-lg ${styles.text}`}
>
<ClockIcon className='w-5 h-5' />
<span className='sr-only'>{statusLabel}</span>
<span>{timeString}</span>
</div>

View File

@@ -12,15 +12,23 @@ const ReminderCard: React.FC<ReminderCardProps> = ({ reminder }) => {
minute: '2-digit',
});
const ReminderIcon = getReminderIcon(reminder.icon);
const titleId = `reminder-${reminder.id}-title`;
return (
<li className='shadow-md rounded-lg p-4 flex flex-col justify-between transition-all duration-300 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700'>
<li
tabIndex={0}
aria-labelledby={titleId}
className='shadow-md rounded-lg p-4 flex flex-col justify-between transition-all duration-300 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sky-400 dark:focus:ring-offset-slate-900'
>
<div>
<div className='flex justify-between items-start'>
<div className='flex items-center space-x-3'>
<ReminderIcon className='w-7 h-7 text-sky-500 dark:text-sky-400 flex-shrink-0' />
<div>
<h4 className='font-bold text-lg text-slate-800 dark:text-slate-100'>
<h4
id={titleId}
className='font-bold text-lg text-slate-800 dark:text-slate-100'
>
{reminder.title}
</h4>
</div>