mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-29 15:15:27 -07:00
a11y: address PR feedback and polish docs
- HistoryTable: skip row key handler when focus is on Actions button (Enter/Space) - StoryList: expose selected story (aria-pressed, 'Selected' in label) - ProfileCard: skip card key handler when focus is on Export/Edit/Delete - VoicesTab: keep table semantics; edit button in first cell instead of role=button on row - PR-ACCESSIBILITY.md: 'Fine-tune' wording, 'focus on the text area' phrasing Made-with: Cursor
This commit is contained in:
@@ -274,7 +274,7 @@ export function HistoryTable() {
|
|||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
if (target.closest('textarea')) return;
|
if (target.closest('textarea') || target.closest('button')) return;
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handlePlay(gen.id, gen.text, gen.profile_id);
|
handlePlay(gen.id, gen.text, gen.profile_id);
|
||||||
|
|||||||
@@ -200,7 +200,12 @@ export function StoryList() {
|
|||||||
'h-24 p-4 border rounded-2xl transition-colors group flex items-center cursor-pointer',
|
'h-24 p-4 border rounded-2xl transition-colors group flex items-center cursor-pointer',
|
||||||
selectedStoryId === story.id && 'bg-muted border-primary',
|
selectedStoryId === story.id && 'bg-muted border-primary',
|
||||||
)}
|
)}
|
||||||
aria-label={`Story ${story.name}, ${story.item_count} ${story.item_count === 1 ? 'item' : 'items'}, ${formatDate(story.updated_at)}. Press Enter to select.`}
|
aria-label={
|
||||||
|
selectedStoryId === story.id
|
||||||
|
? `Story ${story.name}, ${story.item_count} ${story.item_count === 1 ? 'item' : 'items'}, ${formatDate(story.updated_at)}. Selected. Press Enter to select.`
|
||||||
|
: `Story ${story.name}, ${story.item_count} ${story.item_count === 1 ? 'item' : 'items'}, ${formatDate(story.updated_at)}. Press Enter to select.`
|
||||||
|
}
|
||||||
|
aria-pressed={selectedStoryId === story.id}
|
||||||
onClick={() => setSelectedStoryId(story.id)}
|
onClick={() => setSelectedStoryId(story.id)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.target !== e.currentTarget) return;
|
if (e.target !== e.currentTarget) return;
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ export function ProfileCard({ profile }: ProfileCardProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
if (target.closest('button')) return;
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleSelect();
|
handleSelect();
|
||||||
|
|||||||
@@ -183,38 +183,28 @@ function VoiceRow({
|
|||||||
|
|
||||||
const rowLabel = `${profile.name}, ${profile.language}, ${generationCount} generations, ${sampleCount} samples. Press Enter to edit.`;
|
const rowLabel = `${profile.name}, ${profile.language}, ${generationCount} generations, ${sampleCount} samples. Press Enter to edit.`;
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
||||||
const target = e.target as HTMLElement;
|
|
||||||
if (target.closest('button') || target.closest('[role="combobox"]') || target.closest('[role="listbox"]')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
|
||||||
e.preventDefault();
|
|
||||||
onEdit();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow className="cursor-pointer" onClick={onEdit}>
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={onEdit}
|
|
||||||
tabIndex={0}
|
|
||||||
role="button"
|
|
||||||
aria-label={rowLabel}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
>
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex items-center gap-2">
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex w-full min-w-0 items-center gap-2 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded"
|
||||||
|
aria-label={rowLabel}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onEdit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="h-8 w-8 rounded-lg bg-muted flex items-center justify-center shrink-0">
|
<div className="h-8 w-8 rounded-lg bg-muted flex items-center justify-center shrink-0">
|
||||||
<Mic className="h-4 w-4 text-muted-foreground" />
|
<Mic className="h-4 w-4 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="min-w-0">
|
||||||
<div className="font-medium">{profile.name}</div>
|
<div className="font-medium truncate">{profile.name}</div>
|
||||||
{profile.description && (
|
{profile.description && (
|
||||||
<div className="text-sm text-muted-foreground">{profile.description}</div>
|
<div className="text-sm text-muted-foreground truncate">{profile.description}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell onClick={(e) => e.stopPropagation()}>{profile.language}</TableCell>
|
<TableCell onClick={(e) => e.stopPropagation()}>{profile.language}</TableCell>
|
||||||
<TableCell onClick={(e) => e.stopPropagation()}>{generationCount}</TableCell>
|
<TableCell onClick={(e) => e.stopPropagation()}>{generationCount}</TableCell>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Improvements to support screen reader and keyboard users across the main app sur
|
|||||||
|
|
||||||
### Generation UI (text box and voice choice)
|
### Generation UI (text box and voice choice)
|
||||||
|
|
||||||
- **Generate speech** (submit) and **Fine tune instructions** (sliders) – Icon buttons now have `aria-label` (and state for fine-tune, e.g. "Fine tune instructions, on").
|
- **Generate speech** (submit) and **Fine-tune instructions** (sliders) – Icon buttons now have `aria-label` (and state for fine-tune, e.g. "Fine-tune instructions, on").
|
||||||
|
|
||||||
### Voice selection (cards on Generate screen)
|
### Voice selection (cards on Generate screen)
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ Improvements to support screen reader and keyboard users across the main app sur
|
|||||||
### History list (generated samples)
|
### History list (generated samples)
|
||||||
|
|
||||||
- Each **sample row** is focusable with `role="button"` and an `aria-label` (e.g. "Sample from [profile], [duration], [date]. Press Enter to play."); **Enter/Space** plays or restarts.
|
- Each **sample row** is focusable with `role="button"` and an `aria-label` (e.g. "Sample from [profile], [duration], [date]. Press Enter to play."); **Enter/Space** plays or restarts.
|
||||||
- **Transcript textarea** has `aria-label` (e.g. "Transcript for sample from [profile], [duration]") so focus in the text area is announced in context.
|
- **Transcript textarea** has `aria-label` (e.g. "Transcript for sample from [profile], [duration]") so when you focus on the text area, the sample is announced in context.
|
||||||
|
|
||||||
### Voices tab (table)
|
### Voices tab (table)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user