Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions apps/app/src/paths/SiteHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from "react";
import { useEffect, useRef, useState } from "react";
import { Grid, GridItem, SkipNavLink, SkipNavContent } from "@chakra-ui/react";
import { Outlet, useLoaderData } from "react-router";
import { Outlet, useLoaderData, useLocation } from "react-router";
import axios from "axios";
import {
ContentDescription,
Expand All @@ -20,6 +20,7 @@ export type SiteContext = {
setAddTo: (_: ContentDescription | null) => void;
allLicenses: License[];
allDoenetmlVersions: DoenetmlVersion[];
mainRef: React.RefObject<HTMLDivElement | null>;
};

export async function loader() {
Expand All @@ -44,6 +45,20 @@ export async function loader() {
* and a hamburger menu with drill-down navigation on mobile.
* Includes skip navigation link for accessibility.
*/
function ScrollToTop({
scrollRef,
}: {
scrollRef: React.RefObject<HTMLDivElement | null>;
}) {
const { pathname } = useLocation();

useEffect(() => {
scrollRef.current?.scrollTo({ top: 0 });
}, [pathname, scrollRef]);

return null;
}

export function SiteHeader() {
const { user, allLicenses, allDoenetmlVersions } = useLoaderData() as {
user?: UserInfoWithEmail;
Expand All @@ -55,6 +70,8 @@ export function SiteHeader() {

const [addTo, setAddTo] = useState<ContentDescription | null>(null);

const mainRef = useRef<HTMLDivElement>(null);

const siteContext: SiteContext = {
user,
exploreTab,
Expand All @@ -63,6 +80,7 @@ export function SiteHeader() {
setAddTo,
allLicenses,
allDoenetmlVersions,
mainRef,
};

return (
Expand All @@ -84,8 +102,16 @@ export function SiteHeader() {
>
<Navbar user={user} />
</GridItem>
<GridItem as="main" area="main" margin="0" overflowY="auto">
<GridItem
ref={mainRef}
as="main"
area="main"
margin="0"
overflowY="auto"
data-test="Main Content"
>
<SkipNavContent />
<ScrollToTop scrollRef={mainRef} />
<Outlet context={siteContext} />
</GridItem>
</Grid>
Expand Down
41 changes: 41 additions & 0 deletions packages/e2e-tests/e2e/Activities/scrollReset.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
describe("Scroll Reset on Navigation", { tags: ["@group2"] }, function () {
it("resets scroll position when navigating to a different page", () => {
cy.loginAsTestUser();

// Create content so the activities page has items to display
cy.createContent({ name: "Activity 1", doenetML: "Hello!" });
cy.createContent({ name: "Activity 2", doenetML: "World!" });

cy.getUserInfo().then((user) => {
// Use a narrow viewport height so the page is scrollable with just a few items
cy.viewport(1000, 300);

cy.visit(`/activities/${user.userId}`);

// Wait for content to load
cy.get('[data-test="Activities"]').should("exist");

// Verify the main container is scrollable
cy.get('[data-test="Main Content"]').should(($el) => {
expect($el[0].scrollHeight).to.be.greaterThan($el[0].clientHeight);
});

// Scroll down in the main content area
cy.get('[data-test="Main Content"]').scrollTo("bottom");

// Verify we scrolled down
cy.get('[data-test="Main Content"]').should(($el) => {
expect($el[0].scrollTop).to.be.greaterThan(0);
});

// Navigate to Explore
cy.get('[data-test="Explore"]').click();
cy.url().should("include", "/explore");

// Scroll position should be reset to top after navigation
cy.get('[data-test="Main Content"]').should(($el) => {
expect($el[0].scrollTop).to.equal(0);
});
});
});
});
Loading