auto-claude: subtask-1-1 - Add focusedIndex state and arrow key navigation logic

- Added focusedIndex state to track keyboard focus
- Enhanced handleKeyDown to support ArrowDown, ArrowUp, Enter, and Escape keys
- Added visual focus indicator with orange ring (ring-2 ring-orange-500)
- Implemented aria-activedescendant for screen reader support
- Added unique IDs to language options for accessibility
- Reset focusedIndex when dropdown opens/closes

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-25 06:32:13 +01:00
co-authored by Claude Sonnet 4.5
parent eec1758827
commit 5b4c24e737
5 changed files with 327 additions and 6 deletions
+227
View File
@@ -0,0 +1,227 @@
{
"base_commands": [
".",
"[",
"[[",
"ag",
"awk",
"basename",
"bash",
"bc",
"break",
"cat",
"cd",
"chmod",
"clear",
"cmp",
"column",
"comm",
"command",
"continue",
"cp",
"curl",
"cut",
"date",
"df",
"diff",
"dig",
"dirname",
"du",
"echo",
"egrep",
"env",
"eval",
"exec",
"exit",
"expand",
"export",
"expr",
"false",
"fd",
"fgrep",
"file",
"find",
"fmt",
"fold",
"gawk",
"gh",
"git",
"grep",
"gunzip",
"gzip",
"head",
"help",
"host",
"iconv",
"id",
"jobs",
"join",
"jq",
"kill",
"killall",
"less",
"let",
"ln",
"ls",
"lsof",
"man",
"mkdir",
"mktemp",
"more",
"mv",
"nl",
"paste",
"pgrep",
"ping",
"pkill",
"popd",
"printenv",
"printf",
"ps",
"pushd",
"pwd",
"read",
"readlink",
"realpath",
"reset",
"return",
"rev",
"rg",
"rm",
"rmdir",
"sed",
"seq",
"set",
"sh",
"shuf",
"sleep",
"sort",
"source",
"split",
"stat",
"tail",
"tar",
"tee",
"test",
"time",
"timeout",
"touch",
"tr",
"tree",
"true",
"type",
"uname",
"unexpand",
"uniq",
"unset",
"unzip",
"watch",
"wc",
"wget",
"whereis",
"which",
"whoami",
"xargs",
"yes",
"yq",
"zip",
"zsh"
],
"stack_commands": [
"ar",
"clang",
"clang++",
"cmake",
"composer",
"dive",
"docker",
"docker-buildx",
"docker-compose",
"dockerfile",
"eslint",
"g++",
"gcc",
"ipython",
"jupyter",
"ld",
"make",
"meson",
"next",
"ninja",
"nm",
"node",
"notebook",
"npm",
"npx",
"objdump",
"pdb",
"php",
"pip",
"pip3",
"pipx",
"pnpm",
"pnpx",
"pudb",
"python",
"python3",
"react-scripts",
"strip",
"ts-node",
"tsc",
"tsx",
"vitest"
],
"script_commands": [
"bun",
"npm",
"pnpm",
"yarn"
],
"custom_commands": [],
"detected_stack": {
"languages": [
"python",
"javascript",
"typescript",
"php",
"c"
],
"package_managers": [
"pnpm"
],
"frameworks": [
"nextjs",
"react",
"vitest",
"eslint"
],
"databases": [],
"infrastructure": [
"docker"
],
"cloud_providers": [],
"code_quality_tools": [],
"version_managers": []
},
"custom_scripts": {
"npm_scripts": [
"dev",
"build",
"start",
"lint",
"build:images",
"generate:images",
"generate:images:dry",
"test",
"test:coverage"
],
"make_targets": [],
"poetry_scripts": [],
"cargo_aliases": [],
"shell_scripts": []
},
"project_dir": "C:\\Users\\damja\\WebstormProjects\\Portfolio",
"created_at": "2026-01-22T15:28:38.237190",
"project_hash": "c4ad399e16be367eb4e6b076fe1d9ee3",
"inherited_from": "C:\\Users\\damja\\WebstormProjects\\Portfolio"
}
+25
View File
@@ -0,0 +1,25 @@
{
"active": true,
"spec": "011-languageswitcher-arrow-key-navigation",
"state": "planning",
"subtasks": {
"completed": 0,
"total": 0,
"in_progress": 1,
"failed": 0
},
"phase": {
"current": "Add Arrow Key Navigation",
"id": null,
"total": 1
},
"workers": {
"active": 0,
"max": 1
},
"session": {
"number": 2,
"started_at": "2026-01-25T06:26:06.270781"
},
"last_update": "2026-01-25T06:29:49.299484"
}
+39
View File
@@ -0,0 +1,39 @@
{
"sandbox": {
"enabled": true,
"autoAllowBashIfSandboxed": true
},
"permissions": {
"defaultMode": "acceptEdits",
"allow": [
"Read(./**)",
"Write(./**)",
"Edit(./**)",
"Glob(./**)",
"Grep(./**)",
"Read(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\011-languageswitcher-arrow-key-navigation/**)",
"Write(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\011-languageswitcher-arrow-key-navigation/**)",
"Edit(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\011-languageswitcher-arrow-key-navigation/**)",
"Glob(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\011-languageswitcher-arrow-key-navigation/**)",
"Grep(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\011-languageswitcher-arrow-key-navigation/**)",
"Read(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\011-languageswitcher-arrow-key-navigation\\.auto-claude\\specs\\011-languageswitcher-arrow-key-navigation/**)",
"Write(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\011-languageswitcher-arrow-key-navigation\\.auto-claude\\specs\\011-languageswitcher-arrow-key-navigation/**)",
"Edit(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\011-languageswitcher-arrow-key-navigation\\.auto-claude\\specs\\011-languageswitcher-arrow-key-navigation/**)",
"Read(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude/**)",
"Write(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude/**)",
"Edit(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude/**)",
"Glob(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude/**)",
"Grep(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude/**)",
"Bash(*)",
"WebFetch(*)",
"WebSearch(*)",
"mcp__context7__resolve-library-id(*)",
"mcp__context7__get-library-docs(*)",
"mcp__graphiti-memory__search_nodes(*)",
"mcp__graphiti-memory__search_facts(*)",
"mcp__graphiti-memory__add_episode(*)",
"mcp__graphiti-memory__get_episodes(*)",
"mcp__graphiti-memory__get_entity_edge(*)"
]
}
}
+4 -1
View File
@@ -81,4 +81,7 @@ supabase/.temp/
.history/ .history/
# Source images (originals before optimization) # Source images (originals before optimization)
source-images/ source-images/
# Auto Claude data directory
.auto-claude/
+32 -5
View File
@@ -11,6 +11,7 @@ const LanguageSwitcher = () => {
const pathname = usePathname(); const pathname = usePathname();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isMobile, setIsMobile] = useState(false); const [isMobile, setIsMobile] = useState(false);
const [focusedIndex, setFocusedIndex] = useState<number>(-1);
const dropdownRef = useRef<HTMLDivElement>(null); const dropdownRef = useRef<HTMLDivElement>(null);
const languages = [ const languages = [
@@ -30,8 +31,28 @@ const LanguageSwitcher = () => {
}; };
const handleKeyDown = (e: React.KeyboardEvent) => { const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Escape') { if (!isOpen) return;
setIsOpen(false);
switch (e.key) {
case 'Escape':
setIsOpen(false);
setFocusedIndex(-1);
break;
case 'ArrowDown':
e.preventDefault();
setFocusedIndex(prev => (prev + 1) % languages.length);
break;
case 'ArrowUp':
e.preventDefault();
setFocusedIndex(prev => (prev - 1 + languages.length) % languages.length);
break;
case 'Enter':
e.preventDefault();
if (focusedIndex >= 0) {
handleLanguageChange(languages[focusedIndex].code);
setFocusedIndex(-1);
}
break;
} }
}; };
@@ -63,7 +84,10 @@ const LanguageSwitcher = () => {
px-4 py-2 rounded-full transition-all duration-200 px-4 py-2 rounded-full transition-all duration-200
hover:bg-zinc-800/50 border border-transparent hover:border-zinc-800 hover:bg-zinc-800/50 border border-transparent hover:border-zinc-800
hover:scale-105 active:scale-95" hover:scale-105 active:scale-95"
onClick={() => setIsOpen(!isOpen)} onClick={() => {
setIsOpen(!isOpen);
setFocusedIndex(-1);
}}
aria-expanded={isOpen} aria-expanded={isOpen}
aria-haspopup="listbox" aria-haspopup="listbox"
aria-label="Select language" aria-label="Select language"
@@ -79,18 +103,21 @@ const LanguageSwitcher = () => {
${isOpen ? 'opacity-100 scale-100' : 'opacity-0 scale-95 pointer-events-none'}`} ${isOpen ? 'opacity-100 scale-100' : 'opacity-0 scale-95 pointer-events-none'}`}
role="listbox" role="listbox"
aria-label="Languages" aria-label="Languages"
aria-activedescendant={focusedIndex >= 0 ? `lang-option-${languages[focusedIndex].code}` : undefined}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
> >
<div className="py-1"> <div className="py-1">
{languages.map((lang) => ( {languages.map((lang, index) => (
<button <button
key={lang.code} key={lang.code}
id={`lang-option-${lang.code}`}
className={`w-full text-left px-4 py-2 text-sm transition-colors duration-200 className={`w-full text-left px-4 py-2 text-sm transition-colors duration-200
hover:bg-zinc-800/50 hover:bg-zinc-800/50
${locale === lang.code ${locale === lang.code
? 'bg-zinc-800 text-white' ? 'bg-zinc-800 text-white'
: 'text-zinc-400 hover:text-white' : 'text-zinc-400 hover:text-white'
}`} }
${focusedIndex === index ? 'ring-2 ring-orange-500 ring-inset' : ''}`}
onClick={() => handleLanguageChange(lang.code)} onClick={() => handleLanguageChange(lang.code)}
role="option" role="option"
aria-selected={locale === lang.code} aria-selected={locale === lang.code}