commit 194155f5758c11ef3b8af7504427d167282a305b
parent 9d5a9962ffade21615dab330126a387ee6496fb5
Author: Irene Ni <ini@mozilla.com>
Date: Fri, 21 Nov 2025 23:08:49 +0000
Bug 1996923 - Update New Tab top sites to use left/right arrows for consistency. r=home-newtab-reviewers,reemhamz
Differential Revision: https://phabricator.services.mozilla.com/D273114
Diffstat:
3 files changed, 78 insertions(+), 29 deletions(-)
diff --git a/browser/extensions/newtab/content-src/components/TopSites/TopSite.jsx b/browser/extensions/newtab/content-src/components/TopSites/TopSite.jsx
@@ -922,24 +922,22 @@ export class _TopSiteList extends React.PureComponent {
return;
}
- if (e.key === "ArrowDown" || e.key === "ArrowUp") {
- // prevent the page from scrolling up/down while navigating.
- e.preventDefault();
- }
-
- if (
- this.focusedRef?.nextSibling?.querySelector("a") &&
- e.key === "ArrowDown"
- ) {
- this.focusedRef.nextSibling.querySelector("a").tabIndex = 0;
- this.focusedRef.nextSibling.querySelector("a").focus();
- }
- if (
- this.focusedRef?.previousSibling?.querySelector("a") &&
- e.key === "ArrowUp"
- ) {
- this.focusedRef.previousSibling.querySelector("a").tabIndex = 0;
- this.focusedRef.previousSibling.querySelector("a").focus();
+ if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
+ // Arrow direction should match visual navigation direction in RTL
+ const isRTL = document.dir === "rtl";
+ const navigateToPrevious = isRTL
+ ? e.key === "ArrowRight"
+ : e.key === "ArrowLeft";
+
+ const targetTopSite = navigateToPrevious
+ ? this.focusedRef?.previousSibling
+ : this.focusedRef?.nextSibling;
+
+ const targetAnchor = targetTopSite?.querySelector("a");
+ if (targetAnchor) {
+ targetAnchor.tabIndex = 0;
+ targetAnchor.focus();
+ }
}
}
diff --git a/browser/extensions/newtab/data/content/activity-stream.bundle.js b/browser/extensions/newtab/data/content/activity-stream.bundle.js
@@ -9056,17 +9056,16 @@ class _TopSiteList extends (external_React_default()).PureComponent {
if (this.state.activeIndex || this.state.activeIndex === 0) {
return;
}
- if (e.key === "ArrowDown" || e.key === "ArrowUp") {
- // prevent the page from scrolling up/down while navigating.
- e.preventDefault();
- }
- if (this.focusedRef?.nextSibling?.querySelector("a") && e.key === "ArrowDown") {
- this.focusedRef.nextSibling.querySelector("a").tabIndex = 0;
- this.focusedRef.nextSibling.querySelector("a").focus();
- }
- if (this.focusedRef?.previousSibling?.querySelector("a") && e.key === "ArrowUp") {
- this.focusedRef.previousSibling.querySelector("a").tabIndex = 0;
- this.focusedRef.previousSibling.querySelector("a").focus();
+ if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
+ // Arrow direction should match visual navigation direction in RTL
+ const isRTL = document.dir === "rtl";
+ const navigateToPrevious = isRTL ? e.key === "ArrowRight" : e.key === "ArrowLeft";
+ const targetTopSite = navigateToPrevious ? this.focusedRef?.previousSibling : this.focusedRef?.nextSibling;
+ const targetAnchor = targetTopSite?.querySelector("a");
+ if (targetAnchor) {
+ targetAnchor.tabIndex = 0;
+ targetAnchor.focus();
+ }
}
}
onWrapperFocus() {
diff --git a/browser/extensions/newtab/test/unit/content-src/components/TopSites.test.jsx b/browser/extensions/newtab/test/unit/content-src/components/TopSites.test.jsx
@@ -1804,6 +1804,58 @@ describe("<TopSiteList>", () => {
);
assert.lengthOf(wrapper.find("li.hide-for-narrow"), 2);
});
+
+ describe("Keyboard navigation", () => {
+ let sandbox;
+ let wrapper;
+ let instance;
+ let mockAnchor;
+ let mockTargetSibling;
+
+ beforeEach(() => {
+ sandbox = sinon.createSandbox();
+ const rows = [
+ { url: "https://foo.com" },
+ { url: "https://bar.com" },
+ { url: "https://baz.com" },
+ ];
+ wrapper = shallow(
+ <TopSiteList {...DEFAULT_PROPS} TopSites={{ rows }} App={APP} />
+ );
+ instance = wrapper.instance();
+
+ mockAnchor = { focus: sandbox.spy(), tabIndex: -1 };
+ mockTargetSibling = { querySelector: sandbox.stub().returns(mockAnchor) };
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+ it("should navigate to next site with ArrowRight", () => {
+ instance.focusedRef = { nextSibling: mockTargetSibling };
+ const mockEvent = { key: "ArrowRight" };
+
+ instance.onKeyDown(mockEvent);
+
+ assert.calledOnce(mockTargetSibling.querySelector);
+ assert.calledWith(mockTargetSibling.querySelector, "a");
+ assert.calledOnce(mockAnchor.focus);
+ assert.equal(mockAnchor.tabIndex, 0);
+ });
+
+ it("should navigate to previous site with ArrowLeft", () => {
+ instance.focusedRef = { previousSibling: mockTargetSibling };
+ const mockEvent = { key: "ArrowLeft" };
+
+ instance.onKeyDown(mockEvent);
+
+ assert.calledOnce(mockTargetSibling.querySelector);
+ assert.calledWith(mockTargetSibling.querySelector, "a");
+ assert.calledOnce(mockAnchor.focus);
+ assert.equal(mockAnchor.tabIndex, 0);
+ });
+ });
});
describe("TopSiteAddButton", () => {