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:
parent
394000f5ad
commit
640a8836b6
3 changed files with 44 additions and 16 deletions
|
|
@ -73,12 +73,12 @@ export function AIChat({ currentCode }: AIChatProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full bg-card border-l border-border min-w-[260px] max-w-[340px]">
|
<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">
|
<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>
|
<h2 className="font-semibold text-xs tracking-wide uppercase text-muted-foreground">AI Assistant</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ScrollArea className="flex-1 px-2 py-2">
|
<ScrollArea className="flex-1 px-2 py-2" aria-live="polite" aria-label="Chat messages">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2" role="log">
|
||||||
{messages.map((message, index) => (
|
{messages.map((message, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
|
|
@ -122,18 +122,20 @@ export function AIChat({ currentCode }: AIChatProps) {
|
||||||
placeholder="Ask about your code..."
|
placeholder="Ask about your code..."
|
||||||
className="resize-none min-h-[36px] max-h-24 bg-background text-xs px-2 py-1"
|
className="resize-none min-h-[36px] max-h-24 bg-background text-xs px-2 py-1"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
|
aria-label="Chat message input"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
disabled={!input.trim() || isLoading}
|
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"
|
className="bg-accent text-accent-foreground hover:bg-accent/90 btn-accent-hover self-end h-8 w-8 p-0"
|
||||||
tabIndex={-1}
|
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>
|
</Button>
|
||||||
</div>
|
</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
|
Press Enter to send, Shift+Enter for new line
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -147,13 +147,27 @@ export function FileTree({
|
||||||
return (
|
return (
|
||||||
<div key={node.id}>
|
<div key={node.id}>
|
||||||
<div
|
<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}
|
draggable={!isEditing}
|
||||||
onDragStart={(e) => handleDragStart(e, node)}
|
onDragStart={(e) => handleDragStart(e, node)}
|
||||||
onDragOver={(e) => handleDragOver(e, node)}
|
onDragOver={(e) => handleDragOver(e, node)}
|
||||||
onDragLeave={handleDragLeave}
|
onDragLeave={handleDragLeave}
|
||||||
onDrop={(e) => handleDrop(e, node)}
|
onDrop={(e) => handleDrop(e, node)}
|
||||||
onDragEnd={handleDragEnd}
|
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'
|
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' : ''}`}
|
} ${isDragging ? 'opacity-50' : ''} ${isDropTarget && node.type === 'folder' ? 'bg-blue-500/20 border-2 border-blue-500 border-dashed' : ''}`}
|
||||||
style={{ paddingLeft: `${depth * 10 + 8}px` }}
|
style={{ paddingLeft: `${depth * 10 + 8}px` }}
|
||||||
|
|
@ -234,6 +248,7 @@ export function FileTree({
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-6 w-6"
|
className="h-6 w-6"
|
||||||
title="New File"
|
title="New File"
|
||||||
|
aria-label="Create new file"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const name = prompt('Enter file name:');
|
const name = prompt('Enter file name:');
|
||||||
if (name) {
|
if (name) {
|
||||||
|
|
@ -241,7 +256,7 @@ export function FileTree({
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Plus size={14} />
|
<Plus size={14} aria-hidden="true" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<ScrollArea className="flex-1">
|
<ScrollArea className="flex-1">
|
||||||
|
|
|
||||||
|
|
@ -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="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 justify-between px-4 py-2 border-b border-border">
|
||||||
<div className="flex items-center gap-2">
|
<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>
|
<span className="text-sm font-semibold">Search in Files</span>
|
||||||
{results.length > 0 && (
|
{results.length > 0 && (
|
||||||
<Badge variant="secondary" className="text-xs">
|
<Badge variant="secondary" className="text-xs" aria-live="polite">
|
||||||
{results.length} results
|
{results.length} result{results.length !== 1 ? 's' : ''}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-6 w-6">
|
<Button variant="ghost" size="icon" onClick={onClose} className="h-6 w-6" aria-label="Close search panel">
|
||||||
<X size={16} />
|
<X size={16} aria-hidden="true" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -142,9 +142,10 @@ export function SearchInFilesPanel({
|
||||||
if (e.key === 'Enter') handleSearch();
|
if (e.key === 'Enter') handleSearch();
|
||||||
}}
|
}}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
|
aria-label="Search query"
|
||||||
/>
|
/>
|
||||||
<Button onClick={handleSearch} disabled={isSearching || !searchQuery.trim()}>
|
<Button onClick={handleSearch} disabled={isSearching || !searchQuery.trim()} aria-label="Search in files">
|
||||||
<MagnifyingGlass size={16} className="mr-1" />
|
<MagnifyingGlass size={16} className="mr-1" aria-hidden="true" />
|
||||||
Search
|
Search
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -156,6 +157,7 @@ export function SearchInFilesPanel({
|
||||||
checked={caseSensitive}
|
checked={caseSensitive}
|
||||||
onChange={(e) => setCaseSensitive(e.target.checked)}
|
onChange={(e) => setCaseSensitive(e.target.checked)}
|
||||||
className="rounded"
|
className="rounded"
|
||||||
|
aria-label="Case sensitive search"
|
||||||
/>
|
/>
|
||||||
<span>Case sensitive</span>
|
<span>Case sensitive</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -179,8 +181,17 @@ export function SearchInFilesPanel({
|
||||||
{results.map((result, index) => (
|
{results.map((result, index) => (
|
||||||
<div
|
<div
|
||||||
key={`${result.fileId}-${result.line}-${index}`}
|
key={`${result.fileId}-${result.line}-${index}`}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
onClick={() => handleResultClick(result)}
|
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">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<FileText size={14} className="text-muted-foreground flex-shrink-0" />
|
<FileText size={14} className="text-muted-foreground flex-shrink-0" />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue