diff --git a/changelog.md b/changelog.md
index c4379c6..0a769f1 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 `=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
diff --git a/src/components/navigation/NavMenu.stories.tsx b/src/components/navigation/NavMenu.stories.tsx
new file mode 100644
index 0000000..6f2a896
--- /dev/null
+++ b/src/components/navigation/NavMenu.stories.tsx
@@ -0,0 +1,96 @@
+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/Navigation/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 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",
+ children: (
+ <>
+
+ Section Header
+
+
+ }>
+ Button
+
+ >
+ ),
+ },
+ parameters: {
+ 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.",
+ },
+ },
+ },
+};
diff --git a/src/components/navigation/NavMenu.test.tsx b/src/components/navigation/NavMenu.test.tsx
new file mode 100644
index 0000000..178647e
--- /dev/null
+++ b/src/components/navigation/NavMenu.test.tsx
@@ -0,0 +1,128 @@
+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";
+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
+ ,
+ );
+ const menuButton = screen.getByRole("button");
+ expect(screen.queryByText("Link 1")).not.toBeInTheDocument();
+ 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 () => {
+ 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);
+ });
+
+ 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", () => {
+ 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