diff --git a/apps/app/src/paths/FolderContext.cy.tsx b/apps/app/src/paths/FolderContext.cy.tsx
new file mode 100644
index 000000000..6eee8c15b
--- /dev/null
+++ b/apps/app/src/paths/FolderContext.cy.tsx
@@ -0,0 +1,225 @@
+import { FolderContext } from "./FolderContext";
+import { createMemoryRouter, Outlet, RouterProvider } from "react-router";
+import { SiteContext } from "./SiteHeader";
+
+const TEST_USER_ID = "user-abc-123";
+
+const mockContext: SiteContext = {
+ user: {
+ userId: TEST_USER_ID,
+ firstNames: "Test",
+ lastNames: "User",
+ isAnonymous: false,
+ email: null,
+ },
+ exploreTab: null,
+ setExploreTab: () => {},
+ addTo: null,
+ setAddTo: () => {},
+ allLicenses: [],
+ allDoenetmlVersions: [],
+};
+
+/** Wraps FolderContext with a parent that supplies outlet context and a minimal child route. */
+function mountFolderContext(initialPath: string) {
+ function TestParent() {
+ return ;
+ }
+
+ const router = createMemoryRouter(
+ [
+ {
+ element: ,
+ children: [
+ {
+ element: ,
+ children: [
+ {
+ path: `/activities/${TEST_USER_ID}`,
+ element: (
+
Activities Content
+ ),
+ },
+ {
+ path: "/trash",
+ element: Trash Content
,
+ },
+ {
+ path: `/sharedWithMe/${TEST_USER_ID}`,
+ element: Shared Content
,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ { initialEntries: [initialPath] },
+ );
+
+ cy.mount();
+}
+
+describe("FolderContext navigation panel", { tags: ["@group1"] }, () => {
+ describe("navigation link rendering", () => {
+ it("renders all three navigation links", () => {
+ mountFolderContext(`/activities/${TEST_USER_ID}`);
+ cy.get('[data-test="My Activities Link"]').should("be.visible");
+ cy.get('[data-test="Shared With Me Button"]').should("be.visible");
+ cy.get('[data-test="Trash Link"]').should("be.visible");
+ });
+
+ it("renders the outlet content", () => {
+ mountFolderContext(`/activities/${TEST_USER_ID}`);
+ cy.get('[data-test="outlet-content"]').should(
+ "contain.text",
+ "Activities Content",
+ );
+ });
+ });
+
+ describe("active tab marking (aria-current)", () => {
+ it("marks My Activities as current when on activities path", () => {
+ mountFolderContext(`/activities/${TEST_USER_ID}`);
+ cy.get('[data-test="My Activities Link"]').should(
+ "have.attr",
+ "aria-current",
+ "page",
+ );
+ cy.get('[data-test="Shared With Me Button"]').should(
+ "not.have.attr",
+ "aria-current",
+ );
+ cy.get('[data-test="Trash Link"]').should(
+ "not.have.attr",
+ "aria-current",
+ );
+ });
+
+ it("marks Trash as current when on trash path", () => {
+ mountFolderContext("/trash");
+ cy.get('[data-test="Trash Link"]').should(
+ "have.attr",
+ "aria-current",
+ "page",
+ );
+ cy.get('[data-test="My Activities Link"]').should(
+ "not.have.attr",
+ "aria-current",
+ );
+ cy.get('[data-test="Shared With Me Button"]').should(
+ "not.have.attr",
+ "aria-current",
+ );
+ });
+
+ it("marks Shared With Me as current when on sharedWithMe path", () => {
+ mountFolderContext(`/sharedWithMe/${TEST_USER_ID}`);
+ cy.get('[data-test="Shared With Me Button"]').should(
+ "have.attr",
+ "aria-current",
+ "page",
+ );
+ cy.get('[data-test="My Activities Link"]').should(
+ "not.have.attr",
+ "aria-current",
+ );
+ cy.get('[data-test="Trash Link"]').should(
+ "not.have.attr",
+ "aria-current",
+ );
+ });
+ });
+
+ describe("small screen: de-emphasize inactive tabs", () => {
+ beforeEach(() => {
+ cy.viewport(375, 667); // small/mobile screen
+ });
+
+ it("active My Activities tab has full opacity; inactive tabs are de-emphasized", () => {
+ mountFolderContext(`/activities/${TEST_USER_ID}`);
+ cy.get('[data-test="My Activities Link"]').should(
+ "have.css",
+ "opacity",
+ "1",
+ );
+ cy.get('[data-test="Trash Link"]').should("have.css", "opacity", "0.5");
+ cy.get('[data-test="Shared With Me Button"]').should(
+ "have.css",
+ "opacity",
+ "0.5",
+ );
+ });
+
+ it("active Trash tab has full opacity; inactive tabs are de-emphasized", () => {
+ mountFolderContext("/trash");
+ cy.get('[data-test="Trash Link"]').should("have.css", "opacity", "1");
+ cy.get('[data-test="My Activities Link"]').should(
+ "have.css",
+ "opacity",
+ "0.5",
+ );
+ cy.get('[data-test="Shared With Me Button"]').should(
+ "have.css",
+ "opacity",
+ "0.5",
+ );
+ });
+
+ it("active Shared With Me tab has full opacity; inactive tabs are de-emphasized", () => {
+ mountFolderContext(`/sharedWithMe/${TEST_USER_ID}`);
+ cy.get('[data-test="Shared With Me Button"]').should(
+ "have.css",
+ "opacity",
+ "1",
+ );
+ cy.get('[data-test="My Activities Link"]').should(
+ "have.css",
+ "opacity",
+ "0.5",
+ );
+ cy.get('[data-test="Trash Link"]').should("have.css", "opacity", "0.5");
+ });
+ });
+
+ describe("medium screen: all tabs have full opacity", () => {
+ beforeEach(() => {
+ cy.viewport(1024, 768); // medium screen (above md breakpoint)
+ });
+
+ it("all tabs have full opacity regardless of active state", () => {
+ mountFolderContext(`/activities/${TEST_USER_ID}`);
+ cy.get('[data-test="My Activities Link"]').should(
+ "have.css",
+ "opacity",
+ "1",
+ );
+ cy.get('[data-test="Trash Link"]').should("have.css", "opacity", "1");
+ cy.get('[data-test="Shared With Me Button"]').should(
+ "have.css",
+ "opacity",
+ "1",
+ );
+ });
+ });
+
+ describe("accessibility", () => {
+ it("passes accessibility on My Activities page", () => {
+ mountFolderContext(`/activities/${TEST_USER_ID}`);
+ cy.wait(100);
+ cy.checkAccessibility("body");
+ });
+
+ it("passes accessibility on Trash page", () => {
+ mountFolderContext("/trash");
+ cy.wait(100);
+ cy.checkAccessibility("body");
+ });
+
+ it("passes accessibility on small screens", () => {
+ cy.viewport(375, 667);
+ mountFolderContext(`/activities/${TEST_USER_ID}`);
+ cy.wait(100);
+ cy.checkAccessibility("body");
+ });
+ });
+});
diff --git a/apps/app/src/paths/FolderContext.tsx b/apps/app/src/paths/FolderContext.tsx
index 5b4f21fcd..6ec8ad05a 100644
--- a/apps/app/src/paths/FolderContext.tsx
+++ b/apps/app/src/paths/FolderContext.tsx
@@ -22,6 +22,7 @@ export function FolderContext() {
flexShrink={0}
align="flex-start"
borderRight={{ base: "none", md: "solid 2px black" }}
+ borderBottom={{ base: "solid 2px black", md: "none" }}
p={{ base: "0px", xl: "10px" }}
minHeight={{ base: "fit-content", md: "100%" }}
flexDir={{ base: "row", md: "column" }}
@@ -31,7 +32,10 @@ export function FolderContext() {
to={activitiesPath}
variant="ghost"
justifyContent="flex-start"
- width={isActivitiesActive ? "100%" : "calc(100% - 4px)"}
+ width={{
+ base: "auto",
+ md: isActivitiesActive ? "100%" : "calc(100% - 4px)",
+ }}
backgroundColor={
isActivitiesActive ? "doenet.lightBlue" : "transparent"
}
@@ -40,9 +44,14 @@ export function FolderContext() {
? { backgroundColor: "doenet.lightBlue" }
: { backgroundColor: "gray.50" }
}
- borderLeftWidth={isActivitiesActive ? "4px" : "0"}
- marginLeft={isActivitiesActive ? "0" : "4px"}
+ borderLeftWidth={{ base: "0", md: isActivitiesActive ? "4px" : "0" }}
+ marginLeft={{ base: "0", md: isActivitiesActive ? "0" : "4px" }}
borderLeftColor={isActivitiesActive ? "doenet.mainBlue" : "transparent"}
+ borderBottomWidth={{ base: isActivitiesActive ? "3px" : "0", md: "0" }}
+ borderBottomColor={
+ isActivitiesActive ? "doenet.mainBlue" : "transparent"
+ }
+ opacity={{ base: isActivitiesActive ? 1 : 0.5, md: 1 }}
aria-current={isActivitiesActive ? "page" : undefined}
data-test="My Activities Link"
>
@@ -54,7 +63,10 @@ export function FolderContext() {
to={`/sharedWithMe/${context.user?.userId ?? ""}`}
variant="ghost"
justifyContent="flex-start"
- width={isSharedWithMeActive ? "100%" : "calc(100% - 4px)"}
+ width={{
+ base: "auto",
+ md: isSharedWithMeActive ? "100%" : "calc(100% - 4px)",
+ }}
backgroundColor={
isSharedWithMeActive ? "doenet.lightBlue" : "transparent"
}
@@ -63,11 +75,19 @@ export function FolderContext() {
? { backgroundColor: "doenet.lightBlue" }
: { backgroundColor: "gray.50" }
}
- borderLeftWidth={isSharedWithMeActive ? "4px" : "0"}
- marginLeft={isSharedWithMeActive ? "0" : "4px"}
+ borderLeftWidth={{ base: "0", md: isSharedWithMeActive ? "4px" : "0" }}
+ marginLeft={{ base: "0", md: isSharedWithMeActive ? "0" : "4px" }}
borderLeftColor={
isSharedWithMeActive ? "doenet.mainBlue" : "transparent"
}
+ borderBottomWidth={{
+ base: isSharedWithMeActive ? "3px" : "0",
+ md: "0",
+ }}
+ borderBottomColor={
+ isSharedWithMeActive ? "doenet.mainBlue" : "transparent"
+ }
+ opacity={{ base: isSharedWithMeActive ? 1 : 0.5, md: 1 }}
aria-current={isSharedWithMeActive ? "page" : undefined}
data-test="Shared With Me Button"
>
@@ -82,16 +102,22 @@ export function FolderContext() {
to={`/trash`}
variant="ghost"
justifyContent="flex-start"
- width={isTrashActive ? "100%" : "calc(100% - 4px)"}
+ width={{
+ base: "auto",
+ md: isTrashActive ? "100%" : "calc(100% - 4px)",
+ }}
backgroundColor={isTrashActive ? "doenet.lightBlue" : "transparent"}
_hover={
isTrashActive
? { backgroundColor: "doenet.lightBlue" }
: { backgroundColor: "gray.50" }
}
- borderLeftWidth={isTrashActive ? "4px" : "0"}
- marginLeft={isTrashActive ? "0" : "4px"}
+ borderLeftWidth={{ base: "0", md: isTrashActive ? "4px" : "0" }}
+ marginLeft={{ base: "0", md: isTrashActive ? "0" : "4px" }}
borderLeftColor={isTrashActive ? "doenet.mainBlue" : "transparent"}
+ borderBottomWidth={{ base: isTrashActive ? "3px" : "0", md: "0" }}
+ borderBottomColor={isTrashActive ? "doenet.mainBlue" : "transparent"}
+ opacity={{ base: isTrashActive ? 1 : 0.5, md: 1 }}
aria-current={isTrashActive ? "page" : undefined}
data-test="Trash Link"
>
diff --git a/package-lock.json b/package-lock.json
index 6165508f7..1817d9afa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4352,9 +4352,6 @@
"cpu": [
"arm"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -4368,9 +4365,6 @@
"cpu": [
"arm"
],
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -4384,9 +4378,6 @@
"cpu": [
"arm64"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -4400,9 +4391,6 @@
"cpu": [
"arm64"
],
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -4416,9 +4404,6 @@
"cpu": [
"loong64"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -4432,9 +4417,6 @@
"cpu": [
"loong64"
],
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -4448,9 +4430,6 @@
"cpu": [
"ppc64"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -4464,9 +4443,6 @@
"cpu": [
"ppc64"
],
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -4480,9 +4456,6 @@
"cpu": [
"riscv64"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -4496,9 +4469,6 @@
"cpu": [
"riscv64"
],
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -4512,9 +4482,6 @@
"cpu": [
"s390x"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -4528,9 +4495,6 @@
"cpu": [
"x64"
],
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -4544,9 +4508,6 @@
"cpu": [
"x64"
],
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [