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 (
+
+ );
+ },
+);
+
+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 (
+ <>
+
+
+ >
+ );
+};
+
+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
+
+
+ }>
+ Button
+
+ >
+ ),
+ },
+ 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 (
} />
+
+ ,
+ );
+ 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 (
<>