Skip to content

fix(ui-kit): make Carousel's keyboard handler orientation-aware#8337

Merged
JSONbored merged 4 commits into
JSONbored:mainfrom
shin-core:fix/carousel-vertical-arrow-keys-8308
Jul 24, 2026
Merged

fix(ui-kit): make Carousel's keyboard handler orientation-aware#8337
JSONbored merged 4 commits into
JSONbored:mainfrom
shin-core:fix/carousel-vertical-arrow-keys-8308

Conversation

@shin-core

Copy link
Copy Markdown
Contributor

What & why

Closes #8308.

Carousel (packages/loopover-ui-kit/src/components/carousel.tsx) accepts orientation?: "horizontal" | "vertical", and every other part of the component branches on it — CarouselContent (-ml-4 vs -mt-4 flex-col), CarouselItem (pl-4 vs pt-4), and CarouselPrevious/CarouselNext (reposition + rotate 90°). The keyboard handler was the one exception: handleKeyDown was registered unconditionally and only responded to ArrowLeft/ArrowRight.

So on a orientation="vertical" carousel — whose prev/next buttons already point up/down — ArrowUp/ArrowDown did nothing, while ArrowLeft/ArrowRight (no on-screen meaning in that layout) still scrolled. A keyboard user got no arrow navigation matching what they see.

The fix

Resolve orientation once — reusing the exact expression CarouselContext already exposes (orientation || (opts?.axis === "y" ? "vertical" : "horizontal")), not a second independent check — and branch handleKeyDown on it:

  • vertical: ArrowUpscrollPrev(), ArrowDownscrollNext()
  • horizontal (default, unchanged): ArrowLeftscrollPrev(), ArrowRightscrollNext()

scrollPrev/scrollNext, the Embla wiring, and all non-keyboard behavior are untouched.

Tests (carousel.test.tsx — this component's first tests)

Mocking embla-carousel-react so scrollPrev/scrollNext are spies:

  • vertical: ArrowUp/ArrowDown call scrollPrev/scrollNext; ArrowLeft/ArrowRight do not scroll.
  • horizontal: ArrowLeft/ArrowRight unchanged; ArrowUp/ArrowDown do not scroll.

Verified bug-catching: reverting to the hardcoded handler fails the two vertical cases.

Validation

  • @loopover/ui-kit: tsc build exit 0; prettier --check clean; vitest run (4 tests) pass.
  • Per the issue, packages/loopover-ui-kit is not in the root coverage.include and isn't Codecov-gated; the acceptance bar is its own suite running green. Branched off current main, mergeable-clean.

Carousel accepts orientation="horizontal"|"vertical" and every other part of the
component branches on it (content margins, item padding, prev/next button position
and rotation), but handleKeyDown was hardcoded to ArrowLeft/ArrowRight regardless.
On a vertical carousel -- whose prev/next buttons already point up/down -- ArrowUp/
ArrowDown did nothing while ArrowLeft/ArrowRight (meaningless in that layout) still
scrolled. Resolve orientation once (reusing the exact expression CarouselContext
already exposes) and bind ArrowUp/ArrowDown -> scrollPrev/scrollNext for vertical,
keeping ArrowLeft/ArrowRight for horizontal (the unchanged default). Adds carousel.tsx's
first tests: vertical uses Up/Down (not Left/Right), horizontal unchanged.
@shin-core
shin-core requested a review from JSONbored as a code owner July 24, 2026 10:36
@superagent-security superagent-security Bot added the contributor:flagged Contributor flagged for review by trust analysis. label Jul 24, 2026
@superagent-security

Copy link
Copy Markdown
Contributor

🚨 Contributor flagged. Click here for more info: Superagent Dashboard

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 24, 2026
@loopover-orb

loopover-orb Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Warning

⏸️ LoopOver review result - manual review recommended

Review updated: 2026-07-24 15:00:49 UTC

2 files · 1 AI reviewer · no blockers · CI green · unstable

⏸️ Suggested Action - Manual Review

Review summary
This correctly fixes the keyboard handler in carousel.tsx to branch on orientation, reusing the exact resolvedOrientation expression already computed for the context provider rather than duplicating the check. The fix is well-scoped, keeps horizontal behavior unchanged, and the new test suite exercises all four key/orientation combinations with a real assertion that scrollPrev/scrollNext are (not) called via a mocked Embla API. This is the component's first test file, closes #8308, and the diff coheres cleanly with its stated intent.

Nits — 3 non-blocking
  • The failed 'Contributor trust' check has no detail provided and, per BASE BRANCH STATUS, this branch is 1 commit behind default — the failure cause can't be verified from the diff and may be unrelated to this PR's code.
  • carousel.tsx:88-91 the resolvedOrientation memo isn't wrapped in useMemo, so it recomputes every render, though this is a trivial expression so it's not a real perf concern.
  • Consider wrapping resolvedOrientation in useMemo purely for consistency with the other useCallback-heavy hooks in this file, though it's not required given the cheap computation (carousel.tsx:88-91).
Flagged checks (non-blocking)
  • Contributor trust — Contributor flagged for review

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #8308
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 63 registered-repo PR(s), 30 merged, 0 issue(s).
Contributor context ✅ Confirmed Gittensor contributor shin-core; Gittensor profile; 63 PR(s), 0 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Linked issue satisfaction

Addressed
The diff makes handleKeyDown branch on the same resolvedOrientation expression used by CarouselContext, mapping ArrowUp/ArrowDown to scrollPrev/scrollNext for vertical and preserving ArrowLeft/ArrowRight for horizontal, exactly matching the issue's requirements. It also adds carousel.test.tsx with the requested regression tests covering both vertical (ArrowUp/Down scroll, ArrowLeft/Right don't) an

Review context
  • Author: shin-core
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: not available
  • Official Gittensor activity: 63 PR(s), 0 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Triage stale or unlinked PRs.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

Visual preview
Route Viewport Before (production) After (this PR's preview) Diff
/ desktop before /
before /
after /
after /
/ mobile before / (mobile)
before / (mobile)
after / (mobile)
after / (mobile)

Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy.

Scroll preview
Route Before (production) After (this PR's preview)
/ before / (scroll)
before / (scroll)
after / (scroll)
after / (scroll)

A short scroll-through clip (desktop) — click either thumbnail to open the full animation. Evidence for scroll-linked behavior a single screenshot can't show.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@JSONbored
JSONbored merged commit 08d093a into JSONbored:main Jul 24, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor:flagged Contributor flagged for review by trust analysis. gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

2 participants