From 6ba3e4292b3369e439573e397e805ec86824cd22 Mon Sep 17 00:00:00 2001 From: Avinash Gundimeda <23102a020054@mbu.asia> Date: Wed, 1 Jul 2026 12:06:45 +0530 Subject: [PATCH] feat: add hjkl navigation alongside the arrow keys (#29) Layer vim-style h/j/k/l onto the existing arrow navigation across the sidebar, results, downloads, and seeding. Arrows unchanged. --- preview/browse.svg | 24 ++++++++++++++---------- preview/downloads.svg | 6 +++--- src/ui/App.tsx | 4 ++-- src/ui/components/Downloads.tsx | 4 ++-- src/ui/components/Results.tsx | 4 ++-- src/ui/components/Seeding.tsx | 4 ++-- src/ui/components/Sidebar.tsx | 6 +++--- src/ui/keymap.ts | 4 ++-- 8 files changed, 30 insertions(+), 26 deletions(-) diff --git a/preview/browse.svg b/preview/browse.svg index d400c2e..a410adc 100644 --- a/preview/browse.svg +++ b/preview/browse.svg @@ -107,15 +107,19 @@ SUB ╰───────────────────────────────────────────────────────────╯ - d - Download - / - Search - m - Paste magnet - tab - Switch pane - ? - Keys + ↑↓←→ hjkl + Move + d + Download + y + Copy + s + Sort + / + Search + tab + Switch + ? + Keys \ No newline at end of file diff --git a/preview/downloads.svg b/preview/downloads.svg index 3f7c54f..7c8c339 100644 --- a/preview/downloads.svg +++ b/preview/downloads.svg @@ -105,9 +105,9 @@ c Cancel tab - Switch pane - ? - Keys + Switch + ? + Keys diff --git a/src/ui/App.tsx b/src/ui/App.tsx index f028f9e..b9122b5 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -351,11 +351,11 @@ export function App({ setRegion(region === "sidebar" ? "content" : "sidebar"); return; } - if (key.rightArrow) { + if (key.rightArrow || input === "l") { if (region === "sidebar") setRegion("content"); return; } - if (key.leftArrow) { + if (key.leftArrow || input === "h") { if (region === "content") setRegion("sidebar"); return; } diff --git a/src/ui/components/Downloads.tsx b/src/ui/components/Downloads.tsx index 1a10d2a..99bc965 100644 --- a/src/ui/components/Downloads.tsx +++ b/src/ui/components/Downloads.tsx @@ -57,8 +57,8 @@ export function Downloads() { useInput( (input, key) => { - if (key.upArrow) setCursor(wrapStep(clamped, -1, total)); - else if (key.downArrow) setCursor(wrapStep(clamped, 1, total)); + if (key.upArrow || input === "k") setCursor(wrapStep(clamped, -1, total)); + else if (key.downArrow || input === "j") setCursor(wrapStep(clamped, 1, total)); else if (input === "f") queue.retryFailed(); else if (input === "x") queue.clearHistory(); else if (inActive) { diff --git a/src/ui/components/Results.tsx b/src/ui/components/Results.tsx index 1ef3b78..7e13349 100644 --- a/src/ui/components/Results.tsx +++ b/src/ui/components/Results.tsx @@ -177,13 +177,13 @@ export function Results() { setMode("search"); return; } - if (key.upArrow) { + if (key.upArrow || input === "k") { if (results.length > 0 && clamped > 0) setCursor(clamped - 1); else setMode("search"); return; } if (results.length === 0) return; - if (key.downArrow) setCursor(wrapStep(clamped, 1, results.length)); + if (key.downArrow || input === "j") setCursor(wrapStep(clamped, 1, results.length)); else if (key.pageUp) setCursor(Math.max(0, clamped - pageJump)); else if (key.pageDown) setCursor(Math.min(results.length - 1, clamped + pageJump)); else if (key.return) { diff --git a/src/ui/components/Seeding.tsx b/src/ui/components/Seeding.tsx index 0dab2a3..258b74a 100644 --- a/src/ui/components/Seeding.tsx +++ b/src/ui/components/Seeding.tsx @@ -48,8 +48,8 @@ export function Seeding() { useInput( (input, key) => { - if (key.upArrow) setCursor(wrapStep(clamped, -1, total)); - else if (key.downArrow) setCursor(wrapStep(clamped, 1, total)); + if (key.upArrow || input === "k") setCursor(wrapStep(clamped, -1, total)); + else if (key.downArrow || input === "j") setCursor(wrapStep(clamped, 1, total)); else if (input === "p") { const h = history[clamped]; if (!h) return; diff --git a/src/ui/components/Sidebar.tsx b/src/ui/components/Sidebar.tsx index d3681a3..5216250 100644 --- a/src/ui/components/Sidebar.tsx +++ b/src/ui/components/Sidebar.tsx @@ -37,9 +37,9 @@ export function Sidebar() { const seeding = queue.seedingCount; useInput( - (_input, key) => { - if (key.upArrow) setSection(NAV[wrapStep(idx, -1, NAV.length)]!.key); - else if (key.downArrow) setSection(NAV[wrapStep(idx, 1, NAV.length)]!.key); + (input, key) => { + if (key.upArrow || input === "k") setSection(NAV[wrapStep(idx, -1, NAV.length)]!.key); + else if (key.downArrow || input === "j") setSection(NAV[wrapStep(idx, 1, NAV.length)]!.key); else if (key.return) setRegion("content"); }, { isActive: focused }, diff --git a/src/ui/keymap.ts b/src/ui/keymap.ts index 8729cd0..b6719e8 100644 --- a/src/ui/keymap.ts +++ b/src/ui/keymap.ts @@ -14,7 +14,7 @@ export const HELP_GROUPS: HelpGroup[] = [ { title: "Navigate", hints: [ - { keys: "↑ ↓ ← →", label: "Navigate content and panes" }, + { keys: "↑ ↓ ← →, h j k l", label: "Navigate content and panes" }, { keys: "↵", label: "Open" }, { keys: "tab", label: "Switch pane" }, { keys: "esc", label: "Back" }, @@ -53,7 +53,7 @@ export const HELP_GROUPS: HelpGroup[] = [ // Footer labels stay terse so the contextual hint row never wraps; the `?` // overlay (HELP_GROUPS) carries the full, descriptive list. -const NAVIGATE: Hint = { keys: "↑↓←→", label: "Move" }; +const NAVIGATE: Hint = { keys: "↑↓←→ hjkl", label: "Move" }; const ALWAYS: Hint = { keys: "?", label: "Keys" };