From 95cd65ae5f9369a81fdd7e42029d65b3b8e2b875 Mon Sep 17 00:00:00 2001 From: James Gilbert Date: Thu, 13 Nov 2025 11:50:54 +0000 Subject: [PATCH 01/11] Add NavMenu component --- src/components/controls/NavMenu.tsx | 130 +++++++++++++++++++++++++++ src/components/navigation/Navbar.tsx | 2 +- src/index.ts | 1 + 3 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 src/components/controls/NavMenu.tsx diff --git a/src/components/controls/NavMenu.tsx b/src/components/controls/NavMenu.tsx new file mode 100644 index 0000000..0bf6d65 --- /dev/null +++ b/src/components/controls/NavMenu.tsx @@ -0,0 +1,130 @@ +import { + Typography, + Menu, + Button, + useTheme, + type MenuListProps, + MenuItem, + type MenuItemProps, + Link, + LinkProps, +} from "@mui/material"; +import React, { useState, forwardRef } from "react"; +import { ExpandMore } from "@mui/icons-material"; + +type NavMenuLinkProps = MenuItemProps & LinkProps; + +const NavMenuLink = forwardRef( + function NavMenuLink({ children, ...props }: NavMenuLinkProps, ref) { + const theme = useTheme(); + + return ( + + {children} + + ); + }, +); + +interface NavMenuProps extends MenuListProps { + label: string; +} + +const NavMenu = ({ label, children }: NavMenuProps) => { + const [anchorElement, setAnchorElement] = useState(null); + const open = Boolean(anchorElement); + const [menuWidth, setMenuWidth] = useState(0); + + const openMenu = (e: React.MouseEvent) => { + if (!open) { + setAnchorElement(e.currentTarget); + setMenuWidth(e.currentTarget.offsetWidth); + } + }; + + const closeMenu = () => { + setAnchorElement(null); + }; + + const theme = useTheme(); + + return ( + <> + + + {children} + + + ); +}; + +export { NavMenu, NavMenuLink }; diff --git a/src/components/navigation/Navbar.tsx b/src/components/navigation/Navbar.tsx index f233c67..af81ab5 100644 --- a/src/components/navigation/Navbar.tsx +++ b/src/components/navigation/Navbar.tsx @@ -187,4 +187,4 @@ const Navbar = ({ }; export { Navbar, NavLinks, NavLink }; -export type { NavLinksProps, NavbarProps }; +export type { NavLinkProps, NavLinksProps, NavbarProps }; diff --git a/src/index.ts b/src/index.ts index 8277145..c039453 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ export * from "./components/controls/Logo"; export * from "./components/controls/User"; export * from "./components/controls/ScrollableImages"; export * from "./components/controls/VisitInput"; +export * from "./components/controls/NavMenu"; // components/systems export * from "./components/systems/auth"; From 649ed962999fe73cfe59e0afea6ee4ea349ff76d Mon Sep 17 00:00:00 2001 From: James Gilbert Date: Mon, 17 Nov 2025 09:04:21 +0000 Subject: [PATCH 02/11] Add stories for NavMenu --- src/components/controls/NavMenu.stories.tsx | 72 ++++++++++++++++++++ src/components/navigation/Navbar.stories.tsx | 20 ++++++ 2 files changed, 92 insertions(+) create mode 100644 src/components/controls/NavMenu.stories.tsx diff --git a/src/components/controls/NavMenu.stories.tsx b/src/components/controls/NavMenu.stories.tsx new file mode 100644 index 0000000..8340866 --- /dev/null +++ b/src/components/controls/NavMenu.stories.tsx @@ -0,0 +1,72 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { NavMenu, NavMenuLink } from "./NavMenu"; +import { Button, Divider, Typography } from "@mui/material"; +import { Autorenew } from "@mui/icons-material"; + +const meta: Meta = { + title: "Components/Controls/NavMenu", + component: NavMenu, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: + "A dropdown menu for the Navbar. Can contain multiple `NavMenuLink`s that can be navigated between using the mouse or the keyboard.", + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const BasicMenu: Story = { + args: { + label: "NavMenu", + children: ( + <> + First Link + Second Link + Third Link + + ), + }, + parameters: { + docs: { + description: { + story: + 'A NavMenu populated with `NavMenuLink`s. The menu text is set using `label: "NavMenu"`.', + }, + }, + }, +}; + +export const CustomChildren: Story = { + args: { + label: "NavMenu", + children: ( + <> + + Section Header + + + + + ), + }, + parameters: { + docs: { + description: { + story: + "A NavMenu may contain components other than NavMenuLinks. This one has a section header (made using a `Typography` and a `Divider`) and a button.", + }, + }, + }, +}; diff --git a/src/components/navigation/Navbar.stories.tsx b/src/components/navigation/Navbar.stories.tsx index 5aec179..05207d2 100644 --- a/src/components/navigation/Navbar.stories.tsx +++ b/src/components/navigation/Navbar.stories.tsx @@ -8,6 +8,7 @@ import { ColourSchemeButton } from "../controls/ColourSchemeButton"; import { User } from "../controls/User"; import { MockLink } from "../../utils/MockLink"; import { Logo } from "../controls/Logo"; +import { NavMenu, NavMenuLink } from "../controls/NavMenu"; const meta: Meta = { title: "Components/Navigation/Navbar", @@ -124,6 +125,25 @@ export const LinksAndUser: Story = { }, }; +export const WithLinksInMenu: Story = { + args: { + leftSlot: ( + + First Link + Second Link + Third Link + + ), + }, + parameters: { + docs: { + description: { + story: "The `NavMenu` component is used to contain multiple links.", + }, + }, + }, +}; + export const WithThemeLogo: Story = { args: { children: ( From 17773945209b2d22778e6abb6f23624997c7649b Mon Sep 17 00:00:00 2001 From: James Gilbert Date: Thu, 27 Nov 2025 10:00:56 +0000 Subject: [PATCH 03/11] Move component to navigation directory --- src/components/{controls => navigation}/NavMenu.stories.tsx | 0 src/components/{controls => navigation}/NavMenu.tsx | 0 src/components/navigation/Navbar.stories.tsx | 2 +- src/index.ts | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename src/components/{controls => navigation}/NavMenu.stories.tsx (100%) rename src/components/{controls => navigation}/NavMenu.tsx (100%) diff --git a/src/components/controls/NavMenu.stories.tsx b/src/components/navigation/NavMenu.stories.tsx similarity index 100% rename from src/components/controls/NavMenu.stories.tsx rename to src/components/navigation/NavMenu.stories.tsx diff --git a/src/components/controls/NavMenu.tsx b/src/components/navigation/NavMenu.tsx similarity index 100% rename from src/components/controls/NavMenu.tsx rename to src/components/navigation/NavMenu.tsx diff --git a/src/components/navigation/Navbar.stories.tsx b/src/components/navigation/Navbar.stories.tsx index 05207d2..67a9a74 100644 --- a/src/components/navigation/Navbar.stories.tsx +++ b/src/components/navigation/Navbar.stories.tsx @@ -8,7 +8,7 @@ import { ColourSchemeButton } from "../controls/ColourSchemeButton"; import { User } from "../controls/User"; import { MockLink } from "../../utils/MockLink"; import { Logo } from "../controls/Logo"; -import { NavMenu, NavMenuLink } from "../controls/NavMenu"; +import { NavMenu, NavMenuLink } from "../navigation/NavMenu"; const meta: Meta = { title: "Components/Navigation/Navbar", diff --git a/src/index.ts b/src/index.ts index c039453..4604c42 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ export * from "./components/navigation/Breadcrumbs"; export * from "./components/navigation/Footer"; export * from "./components/navigation/Navbar"; +export * from "./components/navigation/NavMenu"; // components/controls export * from "./components/controls/AppTitlebar"; @@ -12,7 +13,6 @@ export * from "./components/controls/Logo"; export * from "./components/controls/User"; export * from "./components/controls/ScrollableImages"; export * from "./components/controls/VisitInput"; -export * from "./components/controls/NavMenu"; // components/systems export * from "./components/systems/auth"; From dfcb534118e654c59577a3cfdcba162ba4ee84c5 Mon Sep 17 00:00:00 2001 From: James Gilbert Date: Thu, 27 Nov 2025 10:04:55 +0000 Subject: [PATCH 04/11] Modify NavMenuLink to support router links --- src/components/navigation/NavMenu.stories.tsx | 26 ++++++++++++++++++- src/components/navigation/NavMenu.tsx | 16 +++++------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/components/navigation/NavMenu.stories.tsx b/src/components/navigation/NavMenu.stories.tsx index 8340866..c8f5378 100644 --- a/src/components/navigation/NavMenu.stories.tsx +++ b/src/components/navigation/NavMenu.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { NavMenu, NavMenuLink } from "./NavMenu"; import { Button, Divider, Typography } from "@mui/material"; import { Autorenew } from "@mui/icons-material"; +import { MockLink } from "../../utils/MockLink"; const meta: Meta = { title: "Components/Controls/NavMenu", @@ -41,6 +42,29 @@ export const BasicMenu: Story = { }, }; +export const RouterMenu: Story = { + args: { + label: "NavMenu", + children: ( + <> + + First Route + + + Second Route + + + ), + }, + parameters: { + docs: { + description: { + story: "Like `NavLink`s, `NavMenuLink`s can use routing links too.", + }, + }, + }, +}; + export const CustomChildren: Story = { args: { label: "NavMenu", @@ -65,7 +89,7 @@ export const CustomChildren: Story = { docs: { description: { story: - "A NavMenu may contain components other than NavMenuLinks. This one has a section header (made using a `Typography` and a `Divider`) and a button.", + "A NavMenu may contain components other than `NavMenuLink`s. This one has a section header (made using a `Typography` and a `Divider`) and a button.", }, }, }, diff --git a/src/components/navigation/NavMenu.tsx b/src/components/navigation/NavMenu.tsx index 0bf6d65..9b7a1e9 100644 --- a/src/components/navigation/NavMenu.tsx +++ b/src/components/navigation/NavMenu.tsx @@ -6,13 +6,12 @@ import { type MenuListProps, MenuItem, type MenuItemProps, - Link, - LinkProps, } from "@mui/material"; import React, { useState, forwardRef } from "react"; import { ExpandMore } from "@mui/icons-material"; +import { NavLink, NavLinkProps } from "./Navbar"; -type NavMenuLinkProps = MenuItemProps & LinkProps; +type NavMenuLinkProps = MenuItemProps & NavLinkProps; const NavMenuLink = forwardRef( function NavMenuLink({ children, ...props }: NavMenuLinkProps, ref) { @@ -21,12 +20,13 @@ const NavMenuLink = forwardRef( return ( { onClose={closeMenu} anchorEl={anchorElement} disableAutoFocusItem - MenuListProps={{ - sx: { - minWidth: menuWidth, - }, - }} + MenuListProps={{ sx: { minWidth: menuWidth } }} slotProps={{ paper: { style: { backgroundColor: theme.palette.primary.light } }, }} @@ -127,4 +123,4 @@ const NavMenu = ({ label, children }: NavMenuProps) => { ); }; -export { NavMenu, NavMenuLink }; +export { NavMenu, NavMenuLink, type NavMenuLinkProps, type NavMenuProps }; From 7b3fba652541a40ad1bb155546a5dd7d4ffcadfa Mon Sep 17 00:00:00 2001 From: James Gilbert Date: Thu, 27 Nov 2025 18:09:29 +0000 Subject: [PATCH 05/11] Add user-event package --- package.json | 1 + pnpm-lock.yaml | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/package.json b/package.json index 9eb2726..db0afd4 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "@storybook/test": "^8.4.4", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.1.0", + "@testing-library/user-event": "^14.6.1", "@types/node": "^20.19.21", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f3b6b6d..9acfe33 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,6 +117,9 @@ importers: '@testing-library/react': specifier: ^16.1.0 version: 16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.0) '@types/node': specifier: ^20.19.21 version: 20.19.21 @@ -1981,6 +1984,12 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + '@tootallnate/once@2.0.0': resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} @@ -6788,6 +6797,10 @@ snapshots: dependencies: '@testing-library/dom': 10.4.0 + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.0)': + dependencies: + '@testing-library/dom': 10.4.0 + '@tootallnate/once@2.0.0': optional: true From e2e7e95a193d02a999e3b8761b7f15237b6ed8a3 Mon Sep 17 00:00:00 2001 From: James Gilbert Date: Thu, 27 Nov 2025 18:09:47 +0000 Subject: [PATCH 06/11] Make NavLink forward ref --- src/components/navigation/Navbar.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/components/navigation/Navbar.tsx b/src/components/navigation/Navbar.tsx index af81ab5..cb90574 100644 --- a/src/components/navigation/Navbar.tsx +++ b/src/components/navigation/Navbar.tsx @@ -10,7 +10,7 @@ import { styled, } from "@mui/material"; import { MdMenu, MdClose } from "react-icons/md"; -import React, { useState } from "react"; +import React, { forwardRef, useState } from "react"; import { ImageColourSchemeSwitch, @@ -26,13 +26,10 @@ interface NavLinkProps extends LinkProps { href?: string; } -const NavLink = ({ - children, - linkComponent, - to, - href, - ...props -}: NavLinkProps) => { +const NavLink = forwardRef(function NavLink( + { children, linkComponent, to, href, ...props }: NavLinkProps, + ref, +) { const theme = useTheme(); const shouldUseLinkComponent = linkComponent && to; @@ -44,6 +41,7 @@ const NavLink = ({ return ( ); -}; +}); interface NavLinksProps { children: React.ReactElement | React.ReactElement[]; From 2e290092ae5e37e37c23a161d16b6b80ab58546f Mon Sep 17 00:00:00 2001 From: James Gilbert Date: Thu, 27 Nov 2025 18:10:04 +0000 Subject: [PATCH 07/11] Add tests for NavMenu --- src/components/navigation/NavMenu.test.tsx | 115 +++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/components/navigation/NavMenu.test.tsx diff --git a/src/components/navigation/NavMenu.test.tsx b/src/components/navigation/NavMenu.test.tsx new file mode 100644 index 0000000..0832685 --- /dev/null +++ b/src/components/navigation/NavMenu.test.tsx @@ -0,0 +1,115 @@ +import { screen } from "@testing-library/react"; +import { userEvent } from "@testing-library/user-event"; +import { renderWithProviders } from "../../__test-utils__/helpers"; +import { NavMenu, NavMenuLink } from "./NavMenu"; +import { Link, MemoryRouter, Route, Routes } from "react-router-dom"; +const user = userEvent.setup(); + +describe("NavMenu", () => { + it("should render with a label", () => { + renderWithProviders(); + expect(screen.getByText("Navmenu")).toBeInTheDocument(); + }); + + it("should open when clicked", async () => { + renderWithProviders( + + Link 1 + Link 2 + , + ); + + expect(screen.queryByText("Link 1")).not.toBeInTheDocument(); + await user.click(screen.getByRole("button")); + expect(screen.getByText("Link 1")).toBeVisible(); + expect(screen.getByText("Link 2")).toBeVisible(); + }); + + it("should open when selected using keyboard", async () => { + renderWithProviders( + + Link 1 + , + ); + + expect(screen.queryByText("Link 1")).not.toBeInTheDocument(); + await user.keyboard("[Tab][Enter]"); + expect(screen.getByText("Link 1")).toBeVisible(); + }); + + it("should be possible to access the contents using the keyboard", async () => { + renderWithProviders( + + Link 1 + Link 2 + , + ); + + await user.keyboard("[Tab][Enter][ArrowDown]"); + const link1 = screen.getByRole("menuitem", { name: "Link 1" }); + expect(document.activeElement).toBe(link1); + await user.keyboard("[ArrowDown]"); + const link2 = screen.getByRole("menuitem", { name: "Link 2" }); + expect(document.activeElement).toBe(link2); + }); +}); + +describe("NavMenuLink", () => { + it("should function as a link", () => { + renderWithProviders(Link); + expect(screen.getByRole("menuitem")).toHaveAttribute("href", "/test"); + }); + + it("should accept router link props", () => { + renderWithProviders( + + + Link + + , + ); + expect(screen.getByRole("menuitem")).toHaveAttribute("href", "/test"); + }); + + it("should use routing when clicked", async () => { + renderWithProviders( + + + + Link + + } + /> + Second page

} /> +
+
, + ); + await user.click(screen.getByRole("menuitem")); + expect(screen.getByText("Second page")).toBeInTheDocument(); + }); + + it("should use routing on enter key press", async () => { + renderWithProviders( + + + + Link + + } + /> + Second page

} /> +
+
, + ); + const link = screen.getByRole("menuitem"); + link.focus(); + await user.keyboard("[enter]"); + expect(screen.getByText("Second page")).toBeInTheDocument(); + }); +}); From 24247a96453ebed27c8bd33ece8ee2be9d736bb5 Mon Sep 17 00:00:00 2001 From: James Gilbert Date: Fri, 28 Nov 2025 08:54:11 +0000 Subject: [PATCH 08/11] Move stories to navigation --- src/components/navigation/NavMenu.stories.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/navigation/NavMenu.stories.tsx b/src/components/navigation/NavMenu.stories.tsx index c8f5378..6f2a896 100644 --- a/src/components/navigation/NavMenu.stories.tsx +++ b/src/components/navigation/NavMenu.stories.tsx @@ -5,7 +5,7 @@ import { Autorenew } from "@mui/icons-material"; import { MockLink } from "../../utils/MockLink"; const meta: Meta = { - title: "Components/Controls/NavMenu", + title: "Components/Navigation/NavMenu", component: NavMenu, tags: ["autodocs"], parameters: { @@ -36,7 +36,7 @@ export const BasicMenu: Story = { docs: { description: { story: - 'A NavMenu populated with `NavMenuLink`s. The menu text is set using `label: "NavMenu"`.', + 'A `NavMenu` populated with `NavMenuLink`s. The menu text is set using `label: "NavMenu"`.', }, }, }, @@ -89,7 +89,7 @@ export const CustomChildren: Story = { docs: { description: { story: - "A NavMenu may contain components other than `NavMenuLink`s. This one has a section header (made using a `Typography` and a `Divider`) and a button.", + "A `NavMenu` may contain components other than `NavMenuLink`s. This one has a section header (made using a `Typography` and a `Divider`) and a button.", }, }, }, From 9046c3f22645ee5ad765acb8cf274023eaabe8de Mon Sep 17 00:00:00 2001 From: James Gilbert Date: Fri, 28 Nov 2025 09:01:02 +0000 Subject: [PATCH 09/11] Update changelog --- changelog.md | 87 +++++++++++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/changelog.md b/changelog.md index c4379c6..c2c4fcd 100644 --- a/changelog.md +++ b/changelog.md @@ -1,35 +1,37 @@ -SciReactUI Changelog -==================== +# SciReactUI Changelog -[v0.3.1alpha] - 2025-?-? ------------------------- +## [v0.3.1alpha] - 2025-?-? ### Added -- New *Progress* component based on Diamond Light added. -- New *ProgressDelayed* component so that the progress isn't shown at all when it's a small wait. + +- New _Progress_ component based on Diamond Light added. +- New _ProgressDelayed_ component so that the progress isn't shown at all when it's a small wait. +- NavMenu component added for creating dropdown menus in the NavBar + - NavMenuLink component extends NavLink to work in the NavMenu ### Fixed + - Hovering over a slot caused a popup with the slot title in. This has been removed. - Stopped Bar-based components (e.g. Navbar, Footer) from expanding when a parent component has a set height -- The base *Bar* component was not being exported. ### Changed -- Remove first-child css selector as it causes problems with server-side rendering. +- Remove first-child css selector as it causes problems with server-side rendering. -[v0.3.0] - 2025-09-04 ---------------------- +## [v0.3.0] - 2025-09-04 ### Added -- *Logo* component, to easily add the theme logo to anywhere -- *ImageColourSchemeSwitch* takes a parameter *interchange* to swap image based on the opposite -of the colour scheme switch - for use with alternative background colours. -- *BaseBar* component is the base for all the bars used in SciReactUI. Can also be used itself. -- *AppBar* is a bar to show the main title of your App. + +- _Logo_ component, to easily add the theme logo to anywhere +- _ImageColourSchemeSwitch_ takes a parameter _interchange_ to swap image based on the opposite + of the colour scheme switch - for use with alternative background colours. +- _BaseBar_ component is the base for all the bars used in SciReactUI. Can also be used itself. +- _AppBar_ is a bar to show the main title of your App. - JsonForms renderers have been added for use with readonly mode in JsonForms. - Support for TIFFs in ScrollableImages component ### Fixed + - Themes were not inheriting all details from their parents. - Fixed alt text on logos. - Fixed Footer was not adhering to Container width. (Can be turned off with containerWidth setting) @@ -37,39 +39,41 @@ of the colour scheme switch - for use with alternative background colours. - Ordering of StoryBook now more intuitive. ### Changed -- Breaking change: The use of *color* has been replaced with *colour* throughout. - - *ImageColorSchemeSwitch*, *ImageColorSchemeSwitchType* and *ImageColorSchemeSwitchProps* - renamed to *ImageColourSchemeSwitch*, ImageColourSchemeSwitchType and ImageColourSchemeSwitchProps respectively - - *User* component color prop renamed to colour. -- RootProps on *Breadcrumbs* has been removed. There props can be passed in directly. -e.g. `` instead of `` +- Breaking change: The use of _color_ has been replaced with _colour_ throughout. + - _ImageColorSchemeSwitch_, _ImageColorSchemeSwitchType_ and _ImageColorSchemeSwitchProps_ + renamed to _ImageColourSchemeSwitch_, ImageColourSchemeSwitchType and ImageColourSchemeSwitchProps respectively + - _User_ component color prop renamed to colour. +- RootProps on _Breadcrumbs_ has been removed. There props can be passed in directly. + e.g. `` instead of `` -[v0.2.0] - 2025-06-11 ---------------------- +## [v0.2.0] - 2025-06-11 ### Fixed + - Styles added to Navbar and Footer incorrectly remove built in styles. - Logo not appearing when no dark src set in dark mode. ### Changed -- Breadcrumbs component takes optional linkComponent prop for page routing. + +- Breadcrumbs component takes optional linkComponent prop for page routing. - Navbar, NavLink and FooterLink will use routing library for links if provided with linkComponent and to props. - Navbar uses slots for positioning elements. Breaking change: elements must now use rightSlot for positioning to the far right. - User can take additional menu items through the menuItems prop. - Footer uses slots for positioning elements. Breaking change: elements must now use rightSlot for positioning to the far right. ### Added -- ScrollableImages component to scroll through multiple images. +- ScrollableImages component to scroll through multiple images. -[v0.1.0] - 2025-04-10 ---------------------- +## [v0.1.0] - 2025-04-10 ### Added + - Breadcrumbs take object array (CustomLink) for total control over names and links. ### Fixed + - Stopped flicker between colour modes when starting an app in dark mode. - Footer links stopped from moving on hover when only showing links. - Footer links now correctly center horizontally, if needed. @@ -77,29 +81,29 @@ e.g. `` instead of `` instead of ` Date: Thu, 4 Dec 2025 10:22:27 +0000 Subject: [PATCH 10/11] Add accessibility props to NavMenu --- changelog.md | 4 ++-- src/components/navigation/NavMenu.test.tsx | 17 +++++++++++++++-- src/components/navigation/NavMenu.tsx | 7 ++++++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index c2c4fcd..0a769f1 100644 --- a/changelog.md +++ b/changelog.md @@ -6,8 +6,8 @@ - New _Progress_ component based on Diamond Light added. - New _ProgressDelayed_ component so that the progress isn't shown at all when it's a small wait. -- NavMenu component added for creating dropdown menus in the NavBar - - NavMenuLink component extends NavLink to work in the NavMenu +- _NavMenu_ component added for creating dropdown menus in the Navbar + - _NavMenuLink_ component extends NavLink to work in the NavMenu ### Fixed diff --git a/src/components/navigation/NavMenu.test.tsx b/src/components/navigation/NavMenu.test.tsx index 0832685..cf8479c 100644 --- a/src/components/navigation/NavMenu.test.tsx +++ b/src/components/navigation/NavMenu.test.tsx @@ -18,11 +18,13 @@ describe("NavMenu", () => { Link 2
, ); - + const menuButton = screen.getByRole("button"); expect(screen.queryByText("Link 1")).not.toBeInTheDocument(); - await user.click(screen.getByRole("button")); + expect(menuButton).toHaveAttribute("aria-expanded", "false"); + await user.click(menuButton); expect(screen.getByText("Link 1")).toBeVisible(); expect(screen.getByText("Link 2")).toBeVisible(); + expect(menuButton).toHaveAttribute("aria-expanded", "true"); }); it("should open when selected using keyboard", async () => { @@ -52,6 +54,17 @@ describe("NavMenu", () => { const link2 = screen.getByRole("menuitem", { name: "Link 2" }); expect(document.activeElement).toBe(link2); }); + + it("should render with accessibility props", async () => { + renderWithProviders(); + + const menuButton = screen.getByRole("button"); + const buttonControlsId = menuButton.getAttribute("aria-controls"); + expect(menuButton).toHaveAttribute("aria-haspopup", "menu"); + await user.click(menuButton); + const menuId = screen.getByRole("presentation").getAttribute("id"); + expect(buttonControlsId).toEqual(menuId); + }); }); describe("NavMenuLink", () => { diff --git a/src/components/navigation/NavMenu.tsx b/src/components/navigation/NavMenu.tsx index 9b7a1e9..667626e 100644 --- a/src/components/navigation/NavMenu.tsx +++ b/src/components/navigation/NavMenu.tsx @@ -7,7 +7,7 @@ import { MenuItem, type MenuItemProps, } from "@mui/material"; -import React, { useState, forwardRef } from "react"; +import React, { useState, forwardRef, useId } from "react"; import { ExpandMore } from "@mui/icons-material"; import { NavLink, NavLinkProps } from "./Navbar"; @@ -54,6 +54,7 @@ const NavMenu = ({ label, children }: NavMenuProps) => { const [anchorElement, setAnchorElement] = useState(null); const open = Boolean(anchorElement); const [menuWidth, setMenuWidth] = useState(0); + const menuId = useId(); const openMenu = (e: React.MouseEvent) => { if (!open) { @@ -71,6 +72,9 @@ const NavMenu = ({ label, children }: NavMenuProps) => { return ( <> Date: Thu, 4 Dec 2025 11:23:12 +0000 Subject: [PATCH 11/11] Fix missing act() in test --- src/components/navigation/NavMenu.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/navigation/NavMenu.test.tsx b/src/components/navigation/NavMenu.test.tsx index cf8479c..178647e 100644 --- a/src/components/navigation/NavMenu.test.tsx +++ b/src/components/navigation/NavMenu.test.tsx @@ -1,4 +1,4 @@ -import { screen } from "@testing-library/react"; +import { screen, act } from "@testing-library/react"; import { userEvent } from "@testing-library/user-event"; import { renderWithProviders } from "../../__test-utils__/helpers"; import { NavMenu, NavMenuLink } from "./NavMenu"; @@ -121,7 +121,7 @@ describe("NavMenuLink", () => { , ); const link = screen.getByRole("menuitem"); - link.focus(); + act(() => link.focus()); await user.keyboard("[enter]"); expect(screen.getByText("Second page")).toBeInTheDocument(); });