Improve accessibility across components

FileTree:
- Add ARIA labels to new file button
- Add role="button", tabIndex, and keyboard navigation (Enter/Space) for file/folder items
- Add aria-label for expand/collapse folder states
- Add aria-expanded attribute for folders
- Add focus ring styles (focus:ring-2 focus:ring-accent)
- Add aria-hidden to decorative icons

SearchInFilesPanel:
- Add ARIA labels to close button and search button
- Add aria-label to search input
- Add aria-live="polite" to results count badge
- Add keyboard navigation (Enter/Space) to search results
- Add focus ring styles to search results
- Add role="button" to clickable result items
- Add aria-label to case sensitive checkbox
- Add aria-hidden to decorative icons

AIChat:
- Add aria-live="polite" to chat messages area
- Add role="log" to messages container
- Add aria-label to message input textarea
- Add aria-label to send button
- Add role="note" to keyboard shortcut hint
- Add aria-hidden to decorative icons
This commit is contained in:
Claude 2026-01-17 22:31:23 +00:00
parent 394000f5ad
commit 640a8836b6
No known key found for this signature in database
3 changed files with 44 additions and 16 deletions

View file

@ -73,12 +73,12 @@ export function AIChat({ currentCode }: AIChatProps) {
return (
<div className="flex flex-col h-full bg-card border-l border-border min-w-[260px] max-w-[340px]">
<div className="flex items-center gap-2 px-3 py-2 border-b border-border bg-card/80">
<Sparkle className="text-accent" weight="fill" />
<Sparkle className="text-accent" weight="fill" aria-hidden="true" />
<h2 className="font-semibold text-xs tracking-wide uppercase text-muted-foreground">AI Assistant</h2>
</div>
<ScrollArea className="flex-1 px-2 py-2">
<div className="space-y-2">
<ScrollArea className="flex-1 px-2 py-2" aria-live="polite" aria-label="Chat messages">
<div className="space-y-2" role="log">
{messages.map((message, index) => (
<div
key={index}
@ -122,18 +122,20 @@ export function AIChat({ currentCode }: AIChatProps) {
placeholder="Ask about your code..."
className="resize-none min-h-[36px] max-h-24 bg-background text-xs px-2 py-1"
disabled={isLoading}
aria-label="Chat message input"
/>
<Button
onClick={handleSend}
disabled={!input.trim() || isLoading}
className="bg-accent text-accent-foreground hover:bg-accent/90 btn-accent-hover self-end h-8 w-8 p-0"
tabIndex={-1}
title="Send"
title="Send message"
aria-label="Send message"
>
<PaperPlaneRight weight="fill" size={16} />
<PaperPlaneRight weight="fill" size={16} aria-hidden="true" />
</Button>
</div>
<p className="text-[10px] text-muted-foreground mt-1">
<p className="text-[10px] text-muted-foreground mt-1" role="note">
Press Enter to send, Shift+Enter for new line
</p>
</div>

View file

@ -147,13 +147,27 @@ export function FileTree({
return (
<div key={node.id}>
<div
role={node.type === 'folder' ? 'button' : 'button'}
tabIndex={0}
aria-label={node.type === 'folder' ? `${isExpanded ? 'Collapse' : 'Expand'} folder ${node.name}` : `Open file ${node.name}`}
aria-expanded={node.type === 'folder' ? isExpanded : undefined}
draggable={!isEditing}
onDragStart={(e) => handleDragStart(e, node)}
onDragOver={(e) => handleDragOver(e, node)}
onDragLeave={handleDragLeave}
onDrop={(e) => handleDrop(e, node)}
onDragEnd={handleDragEnd}
className={`flex items-center gap-1 px-2 py-1.5 md:py-1 hover:bg-muted/60 cursor-pointer group rounded-sm transition-colors touch-manipulation ${
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
if (node.type === 'folder') {
toggleFolder(node.id);
} else {
onFileSelect(node);
}
}
}}
className={`flex items-center gap-1 px-2 py-1.5 md:py-1 hover:bg-muted/60 cursor-pointer group rounded-sm transition-colors touch-manipulation focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-1 ${
isSelected ? 'bg-accent/30 text-accent font-semibold' : 'text-foreground'
} ${isDragging ? 'opacity-50' : ''} ${isDropTarget && node.type === 'folder' ? 'bg-blue-500/20 border-2 border-blue-500 border-dashed' : ''}`}
style={{ paddingLeft: `${depth * 10 + 8}px` }}
@ -234,6 +248,7 @@ export function FileTree({
size="icon"
className="h-6 w-6"
title="New File"
aria-label="Create new file"
onClick={() => {
const name = prompt('Enter file name:');
if (name) {
@ -241,7 +256,7 @@ export function FileTree({
}
}}
>
<Plus size={14} />
<Plus size={14} aria-hidden="true" />
</Button>
</div>
<ScrollArea className="flex-1">

View file

@ -119,16 +119,16 @@ export function SearchInFilesPanel({
<div className="h-[300px] md:h-[400px] bg-card border-t border-border flex flex-col">
<div className="flex items-center justify-between px-4 py-2 border-b border-border">
<div className="flex items-center gap-2">
<MagnifyingGlass size={18} weight="bold" />
<MagnifyingGlass size={18} weight="bold" aria-hidden="true" />
<span className="text-sm font-semibold">Search in Files</span>
{results.length > 0 && (
<Badge variant="secondary" className="text-xs">
{results.length} results
<Badge variant="secondary" className="text-xs" aria-live="polite">
{results.length} result{results.length !== 1 ? 's' : ''}
</Badge>
)}
</div>
<Button variant="ghost" size="icon" onClick={onClose} className="h-6 w-6">
<X size={16} />
<Button variant="ghost" size="icon" onClick={onClose} className="h-6 w-6" aria-label="Close search panel">
<X size={16} aria-hidden="true" />
</Button>
</div>
@ -142,9 +142,10 @@ export function SearchInFilesPanel({
if (e.key === 'Enter') handleSearch();
}}
className="flex-1"
aria-label="Search query"
/>
<Button onClick={handleSearch} disabled={isSearching || !searchQuery.trim()}>
<MagnifyingGlass size={16} className="mr-1" />
<Button onClick={handleSearch} disabled={isSearching || !searchQuery.trim()} aria-label="Search in files">
<MagnifyingGlass size={16} className="mr-1" aria-hidden="true" />
Search
</Button>
</div>
@ -156,6 +157,7 @@ export function SearchInFilesPanel({
checked={caseSensitive}
onChange={(e) => setCaseSensitive(e.target.checked)}
className="rounded"
aria-label="Case sensitive search"
/>
<span>Case sensitive</span>
</label>
@ -179,8 +181,17 @@ export function SearchInFilesPanel({
{results.map((result, index) => (
<div
key={`${result.fileId}-${result.line}-${index}`}
role="button"
tabIndex={0}
onClick={() => handleResultClick(result)}
className="p-2 rounded hover:bg-muted/60 cursor-pointer group transition-colors border border-transparent hover:border-accent/30"
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleResultClick(result);
}
}}
aria-label={`Open ${result.fileName} at line ${result.line}`}
className="p-2 rounded hover:bg-muted/60 cursor-pointer group transition-colors border border-transparent hover:border-accent/30 focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-1"
>
<div className="flex items-center gap-2 mb-1">
<FileText size={14} className="text-muted-foreground flex-shrink-0" />