Improve accessibility and apply React best practices#31
Conversation
- Add focus-visible ring styles to interactive elements (Card, TabBar, SidebarLink, SidebarSearch, ContentSidebarTrigger) - Add aria-label to icon-only buttons (SidebarSearch collapsed state) - Add aria-hidden to decorative icons (Menu, Search) - Add tabular-nums to numeric values in StatCard for proper alignment - Extend prefers-reduced-motion to cover all animations (dropdown, tooltip, modal) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Memoize SidebarContext value to prevent unnecessary re-renders - Extract useEscapeKey hook to deduplicate escape key listeners - Use ternary conditionals instead of && for clearer rendering intent - Destructure item properties in StatGrid map for better performance - Add aria-hidden to decorative icons in Sidebar and ContentSidebarDrawer - Export new useEscapeKey hook from main entry point Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
🧹 Preview deployment has been cleaned up. |
Pull Request Review: Accessibility and React Best PracticesI've reviewed PR #31 and overall this is excellent work. The changes demonstrate strong attention to accessibility, performance optimization, and code quality. Here's my detailed feedback: ✅ StrengthsAccessibility Improvements
React Performance Optimizations
Code Quality
🔍 Issues & Concerns1. CRITICAL: useEscapeKey Hook Has Stale Closure Bug (src/hooks/useEscapeKey.ts:27)The // In Sidebar.tsx:38-39
const closeSidebar = useCallback(() => setOpen(false), [setOpen]);
useEscapeKey(closeSidebar, isMobile && isOpen);While Recommendation: Use export function useEscapeKey(handler: () => void, enabled = true) {
const handlerRef = useRef(handler);
useEffect(() => {
handlerRef.current = handler;
}, [handler]);
useEffect(() => {
if (!enabled) return;
const handleEscape = (e: KeyboardEvent) => {
if (e.key === "Escape") {
handlerRef.current();
}
};
document.addEventListener("keydown", handleEscape);
return () => document.removeEventListener("keydown", handleEscape);
}, [enabled]); // Only re-run when enabled changes
}This pattern ensures the listener isn't constantly added/removed while keeping the handler always current. 2. Potential Performance Issue: Multiple Event Listeners (src/hooks/useEscapeKey.ts)If multiple components use 3. Missing Unit TestsNo tests were added for the new Recommendation: Add a test file import { renderHook } from '@testing-library/react';
import { useEscapeKey } from './useEscapeKey';
describe('useEscapeKey', () => {
it('calls handler when Escape is pressed and enabled', () => {
const handler = jest.fn();
renderHook(() => useEscapeKey(handler, true));
const event = new KeyboardEvent('keydown', { key: 'Escape' });
document.dispatchEvent(event);
expect(handler).toHaveBeenCalledTimes(1);
});
it('does not call handler when disabled', () => {
const handler = jest.fn();
renderHook(() => useEscapeKey(handler, false));
const event = new KeyboardEvent('keydown', { key: 'Escape' });
document.dispatchEvent(event);
expect(handler).not.toHaveBeenCalled();
});
});💡 Suggestions for Improvement4. StatGrid Destructuring Creates Unnecessary Constant (src/components/StatGrid/StatGrid.tsx:208)const statusClass = statusClasses[status || 'default']This creates a constant that's only used twice in close proximity. The original inline usage was fine and possibly more readable. This is a very minor point and mostly stylistic. 5. Consider Keyboard Event OptionsThe const handleEscape = (e: KeyboardEvent) => {
if (e.key === "Escape") {
e.preventDefault(); // Prevent browser default (like exiting fullscreen)
handler();
}
};However, this could interfere with other escape handlers (like closing a modal that contains a sidebar), so the current behavior is probably correct for this use case. 🔒 Security ReviewNo security concerns identified. Changes are UI-focused with no:
🎯 Performance ConsiderationsPositive impacts:
No negative impacts detected. 📋 Test Coverage AssessmentCurrent state: Repository has minimal test coverage (2 test files found) This PR: No tests added for new hook or modified components Recommendation: While not blocking for merge, consider:
🎨 Design System ComplianceAll changes align with DESIGN_GUIDE.md and CLAUDE.md:
📝 Final RecommendationStatus: Approve with Minor Fixes Recommended This PR significantly improves the library's accessibility and code quality. The only critical issue is the potential stale closure in Action items before merge:
Optional improvements for follow-up:
Excellent work on this PR! The accessibility improvements in particular are very well thought out. |
Summary
aria-labelon icon-only buttons,aria-hiddenon decorative icons,tabular-numsfor numeric values in StatCardprefers-reduced-motionto cover all animations (dropdown, tooltip, modal) — previously only covered skeleton/page-enteruseEscapeKeyhook to deduplicate escape key listeners, use ternary conditionals for clearer rendering, destructure props in StatGrid map callbackTest plan
prefers-reduced-motion: reducedisables all animations (dropdown, tooltip, modal)npm run buildandnpm run lint— both pass🤖 Generated with Claude Code