From 95fb09373c56417a9599d54adea579db3f50531b Mon Sep 17 00:00:00 2001 From: Fauzan Naufan Date: Tue, 15 Sep 2026 17:08:27 +0800 Subject: [PATCH 1/8] [MOL-22399][FN] feat: add submenu rows and columns prop types --- src/navbar/types.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/navbar/types.ts b/src/navbar/types.ts index 945e15ab93..55faa1bf6f 100644 --- a/src/navbar/types.ts +++ b/src/navbar/types.ts @@ -11,6 +11,20 @@ import type { TypographyLinkProps } from "../typography"; export interface NavItemLinkProps extends NavItemCommonProps { itemType?: "link"; subMenu?: NavItemCommonProps[] | undefined; + /** + * Number of columns to lay `subMenu` out in on desktop, as a grid instead + * of a single vertical list. Must be set together with `subMenuRows` — + * if either is omitted, `subMenu` renders as a standard vertical list. + * + * Has no effect on the mobile drawer, which always renders `subMenu` as + * an expandable vertical list. + */ + subMenuColumns?: number | undefined; + /** + * Number of grid rows of `subMenu` visible on desktop before the panel + * becomes vertically scrollable. See `subMenuColumns`. + */ + subMenuRows?: number | undefined; } /** * Props for a custom component slot navigation item. From 72c9f9cd10e975c656ebe72dfb1cfd8528a63111 Mon Sep 17 00:00:00 2001 From: Fauzan Naufan Date: Tue, 15 Sep 2026 17:10:42 +0800 Subject: [PATCH 2/8] [MOL-22399][FN] feat: build submenugrid internal component --- src/navbar/submenu-grid.styles.ts | 44 ++++++++++++ src/navbar/submenu-grid.tsx | 114 ++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 src/navbar/submenu-grid.styles.ts create mode 100644 src/navbar/submenu-grid.tsx diff --git a/src/navbar/submenu-grid.styles.ts b/src/navbar/submenu-grid.styles.ts new file mode 100644 index 0000000000..6b0a629f7e --- /dev/null +++ b/src/navbar/submenu-grid.styles.ts @@ -0,0 +1,44 @@ +import { css } from "@linaria/core"; + +import { Colour, Radius, Spacing } from "../theme"; + +export const tokens = { + grid: { + columns: "--fds-navbarItems-subMenuGrid-columns", + maxHeight: "--fds-navbarItems-subMenuGrid-maxHeight", + }, +} as const; + +// removes the wrapper from the layout tree so `Menu.Section`'s `
    ` becomes +// the direct grid parent of its `
  • ` children +export const measureWrapper = css` + display: contents; +`; + +export const grid = css` + ${tokens.grid.columns}: initial; + ${tokens.grid.maxHeight}: initial; + + display: grid; + grid-template-columns: repeat(var(${tokens.grid.columns}), minmax(0, 1fr)); + column-gap: ${Spacing["spacing-8"]}; + row-gap: ${Spacing["spacing-4"]}; + + max-height: var(${tokens.grid.maxHeight}); + overflow-y: auto; + + &::-webkit-scrollbar { + width: 14px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: ${Colour["bg-inverse-subtlest"]}; + border: 5px solid transparent; + border-radius: ${Radius["full"]}; + background-clip: padding-box; + } +`; diff --git a/src/navbar/submenu-grid.tsx b/src/navbar/submenu-grid.tsx new file mode 100644 index 0000000000..617f296376 --- /dev/null +++ b/src/navbar/submenu-grid.tsx @@ -0,0 +1,114 @@ +import { useRef } from "react"; +import { useResizeDetector } from "react-resize-detector"; + +import { Menu as DesktopMenu } from "../menu"; +import { useIsomorphicLayoutEffect } from "../util"; +import * as styles from "./submenu-grid.styles"; +import type { NavItemCommonProps } from "./types"; + +// comfortable minimum per grid column, and the gap used between columns +const SUBMENU_GRID_COLUMN_WIDTH_PX = 383; +const SUBMENU_GRID_COLUMN_GAP_PX = 8; + +/** + * Recommended `Menu.Content` `maxWidth` for a `columns`-wide `SubMenuGrid` — + * the default 24rem panel cap is sized for a single-column list and gets + * cramped once more than 1-2 columns are requested. + */ +export const getSubMenuGridMaxWidth = (columns: number): number => + columns * SUBMENU_GRID_COLUMN_WIDTH_PX + + (columns - 1) * SUBMENU_GRID_COLUMN_GAP_PX; + +interface Props { + items: NavItemCommonProps[]; + columns: number; + rows: number; +} + +/** + * Lays `items` out in a fixed-column CSS grid, capping the visible height to + * `rows` and enabling vertical scroll for the remainder. + * + * Why DOM measurement (not a fixed row height): items line-clamp to 2 lines, + * so row heights vary with label length. We measure the offset of the first + * item past the visible cutoff rather than assume a uniform row height. + * + * Why a wrapper `div` (not a `ref` on `Menu.Section`): `Menu.Section` doesn't + * forward refs, so its rendered `
      ` is read via `firstElementChild` on a + * `display: contents` wrapper instead of changing the shared `Menu` component. + */ +export const SubMenuGrid = ({ + items, + columns, + rows, +}: Props): JSX.Element => { + // ========================================================================= + // CONST, STATE, REF + // ========================================================================= + const wrapperRef = useRef(null); + + // ========================================================================= + // HELPER FUNCTIONS + // ========================================================================= + const applyGridLayout = () => { + const grid = wrapperRef.current + ?.firstElementChild as HTMLElement | null; + if (!grid) return; + + grid.style.setProperty(styles.tokens.grid.columns, String(columns)); + + const visibleCount = rows * columns; + if (items.length <= visibleCount) { + grid.style.removeProperty(styles.tokens.grid.maxHeight); + return; + } + + const boundaryItem = grid.children[visibleCount] as + | HTMLElement + | undefined; + if (!boundaryItem) return; + + const rowGap = parseFloat(getComputedStyle(grid).rowGap || "0"); + const visibleHeight = boundaryItem.offsetTop - rowGap / 2; + + grid.style.setProperty( + styles.tokens.grid.maxHeight, + `${visibleHeight}px` + ); + }; + + // ========================================================================= + // EFFECTS + // ========================================================================= + useIsomorphicLayoutEffect(() => { + applyGridLayout(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [items.length, columns, rows]); + + useResizeDetector({ + handleWidth: true, + handleHeight: false, + skipOnMount: true, + refreshMode: "throttle", + targetRef: wrapperRef, + onResize: applyGridLayout, + }); + + // ========================================================================= + // RENDER FUNCTIONS + // ========================================================================= + return ( +
      + + {items.map((item, index) => ( + + {item.children} + + ))} + +
      + ); +}; From f9f36fda42f167c74b40ef4b52e90e3105ec2753 Mon Sep 17 00:00:00 2001 From: Fauzan Naufan Date: Tue, 15 Sep 2026 17:11:17 +0800 Subject: [PATCH 3/8] [MOL-22399][FN] feat: wire submenugrid into navbar items --- src/menu/menu-content.styles.ts | 6 ++++- src/menu/menu-content.tsx | 3 +++ src/menu/types.ts | 6 +++++ src/navbar/navbar-items.tsx | 43 +++++++++++++++++++++++---------- 4 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/menu/menu-content.styles.ts b/src/menu/menu-content.styles.ts index 5c26d08aa6..12afe632b9 100644 --- a/src/menu/menu-content.styles.ts +++ b/src/menu/menu-content.styles.ts @@ -13,6 +13,7 @@ export const tokens = { panel: { availableWidth: "--fds-internal-menu-panel-availableWidth", maxHeight: "--fds-internal-menu-panel-maxHeight", + maxWidth: "--fds-internal-menu-panel-maxWidth", overflow: "--fds-internal-menu-panel-overflow", xSpacing: "--fds-internal-menu-panel-xSpacing", }, @@ -43,7 +44,10 @@ export const panel = css` } min-width: min(15rem, var(${tokens.panel.availableWidth})); - max-width: min(24rem, var(${tokens.panel.availableWidth})); + max-width: min( + var(${tokens.panel.maxWidth}, 24rem), + var(${tokens.panel.availableWidth}) + ); max-height: var(${tokens.panel.maxHeight}); overflow-y: var(${tokens.panel.overflow}); diff --git a/src/menu/menu-content.tsx b/src/menu/menu-content.tsx index 10d47df3f5..2e86b2b068 100644 --- a/src/menu/menu-content.tsx +++ b/src/menu/menu-content.tsx @@ -23,6 +23,7 @@ export const MenuContent = ({ "data-testid": testId = "menu-content", overflow, maxHeight, + maxWidth, ...otherProps }: MenuContentProps): JSX.Element => { // ============================================================================= @@ -33,6 +34,8 @@ export const MenuContent = ({ useApplyStyle(panelRef, { [styles.tokens.panel.maxHeight]: maxHeight !== undefined ? `${maxHeight}px` : null, + [styles.tokens.panel.maxWidth]: + maxWidth !== undefined ? `${maxWidth}px` : null, [styles.tokens.panel.overflow]: overflow || null, }); // ============================================================================= diff --git a/src/menu/types.ts b/src/menu/types.ts index 0f9c59f391..92d7c3320a 100644 --- a/src/menu/types.ts +++ b/src/menu/types.ts @@ -45,6 +45,12 @@ export interface MenuContentProps | ReactElement | ReactElement[]; "data-testid"?: string | undefined; + /** + * Maximum width of the panel in pixels. + * + * @default 384 (24rem) + */ + maxWidth?: number | undefined; } // @storybookSkipProps diff --git a/src/navbar/navbar-items.tsx b/src/navbar/navbar-items.tsx index abe86eb02f..7b9a74b784 100644 --- a/src/navbar/navbar-items.tsx +++ b/src/navbar/navbar-items.tsx @@ -9,6 +9,7 @@ import type { TypographyWeight } from "../typography"; import { useId } from "../util"; import { Menu as MobileMenu } from "./menu"; import * as styles from "./navbar-items.styles"; +import { getSubMenuGridMaxWidth, SubMenuGrid } from "./submenu-grid"; import type { NavItemCommonProps, NavItemLinkProps, @@ -133,19 +134,30 @@ export const NavbarItems = ({ // ============================================================================= const renderDesktopSubMenu = ( subMenu: NavItemCommonProps[], - subMenuId: string + subMenuId: string, + columns?: number, + rows?: number ) => ( - - - {subMenu.map((item, subIndex) => ( - - {item.children} - - ))} - + + {columns && rows ? ( + + ) : ( + + {subMenu.map((item, subIndex) => ( + + {item.children} + + ))} + + )} ); @@ -276,7 +288,12 @@ export const NavbarItems = ({ { From 33abe8255cd7384c993df6b76e8c253a7aee9dff Mon Sep 17 00:00:00 2001 From: Fauzan Naufan Date: Tue, 15 Sep 2026 17:20:52 +0800 Subject: [PATCH 4/8] [MOL-22399][FN] test: unit test for navbar submenu grid --- src/navbar/navbar-items.tsx | 9 ++++- tests/jest-setup.ts | 16 ++++++++ tests/navbar/navbar.spec.tsx | 76 ++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/src/navbar/navbar-items.tsx b/src/navbar/navbar-items.tsx index 7b9a74b784..08f59f6c31 100644 --- a/src/navbar/navbar-items.tsx +++ b/src/navbar/navbar-items.tsx @@ -170,7 +170,14 @@ export const NavbarItems = ({ ); const renderLinkItem = (item: NavItemLinkProps, index: number) => { - const { children, options, subMenu, ...otherItemAttrs } = item; + const { + children, + options, + subMenu, + subMenuColumns: _subMenuColumns, + subMenuRows: _subMenuRows, + ...otherItemAttrs + } = item; const hasSubMenu = !!subMenu?.length; diff --git a/tests/jest-setup.ts b/tests/jest-setup.ts index b7c980f84c..511a66f807 100644 --- a/tests/jest-setup.ts +++ b/tests/jest-setup.ts @@ -8,6 +8,22 @@ if (typeof globalThis.PointerEvent === "undefined") { (globalThis as any).PointerEvent = MouseEvent; } +// Polyfill ResizeObserver for jsdom (used by react-resize-detector, e.g. SubMenuGrid) +if (typeof globalThis.ResizeObserver === "undefined") { + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- globalThis lacks ResizeObserver type in jsdom + (globalThis as any).ResizeObserver = class { + public observe() { + // noop for jsdom + } + public unobserve() { + // noop for jsdom + } + public disconnect() { + // noop for jsdom + } + }; +} + const getFallbackThemeContext = (): ThemeContextValue => ({ theme: "lifesg", mode: "light", diff --git a/tests/navbar/navbar.spec.tsx b/tests/navbar/navbar.spec.tsx index a4ee8a2148..54684b6f48 100644 --- a/tests/navbar/navbar.spec.tsx +++ b/tests/navbar/navbar.spec.tsx @@ -1,6 +1,7 @@ import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { Navbar } from "src/navbar"; +import * as subMenuGridStyles from "src/navbar/submenu-grid.styles"; describe("Navbar", () => { describe("Basic functions", () => { @@ -178,6 +179,57 @@ describe("Navbar", () => { ); }); }); + + describe("Submenu grid", () => { + it("should render all subMenu item labels even when the visible cutoff is exceeded", async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByTestId("link__1")); + + for (const label of MOCK_SUBMENU_LABELS) { + expect(screen.getByText(label)).toBeInTheDocument(); + } + }); + + it("should apply the grid layout when subMenuColumns and subMenuRows are both set", async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByTestId("link__1")); + + expect( + document.body.getElementsByClassName(subMenuGridStyles.grid) + ).toHaveLength(1); + }); + + it("should fall back to the vertical list when only one of subMenuColumns/subMenuRows is set", async () => { + const user = userEvent.setup(); + render( + + ); + + await user.click(screen.getByTestId("link__1")); + + for (const label of MOCK_SUBMENU_LABELS) { + expect(screen.getByText(label)).toBeInTheDocument(); + } + expect( + document.body.getElementsByClassName(subMenuGridStyles.grid) + ).toHaveLength(0); + }); + }); }); // ============================================================================= @@ -210,3 +262,27 @@ function MOCK_COLLAPSIBLE_BUTTON() { args: { children: "Uncollapsible" }, }; } + +function MOCK_SUBMENU_ITEMS() { + return [ + { id: "sub-1", children: "Sub item 1", href: "#" }, + { id: "sub-2", children: "Sub item 2", href: "#" }, + { id: "sub-3", children: "Sub item 3", href: "#" }, + { id: "sub-4", children: "Sub item 4", href: "#" }, + { id: "sub-5", children: "Sub item 5", href: "#" }, + ]; +} + +const MOCK_SUBMENU_LABELS = MOCK_SUBMENU_ITEMS().map((item) => item.children); + +function MOCK_GRID_ITEMS() { + return [ + { + id: "guides", + children: "Guides", + subMenuColumns: 2, + subMenuRows: 2, + subMenu: MOCK_SUBMENU_ITEMS(), + }, + ]; +} From 369d1f53abf229251c246b4e9534b5b5ba4a1f6d Mon Sep 17 00:00:00 2001 From: Fauzan Naufan Date: Tue, 15 Sep 2026 17:25:54 +0800 Subject: [PATCH 5/8] [MOL-22399][FN] feat: add story for sub menu grid --- stories/navbar/navbar.stories.tsx | 273 ++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) diff --git a/stories/navbar/navbar.stories.tsx b/stories/navbar/navbar.stories.tsx index 96dd96443c..d4d966e62e 100644 --- a/stories/navbar/navbar.stories.tsx +++ b/stories/navbar/navbar.stories.tsx @@ -388,6 +388,279 @@ export const SubMenu: StoryObj = { }, }; +export const SubMenuGrid: StoryObj = { + render: (_args) => { + const [selected, setSelected] = useState("lifesg1"); + return ( + { + setSelected((item as NavItemLinkProps).id); + }} + /> + ); + }, +}; + export const SecondaryBranding: StoryObj = { render: (_args) => { return ( From 322c20067fdbb2ec82daa0bccd7cbab5af1b4ba9 Mon Sep 17 00:00:00 2001 From: Fauzan Naufan Date: Thu, 17 Sep 2026 10:31:04 +0800 Subject: [PATCH 6/8] [MOL-22399][FN] test: add e2e for navbar sumenu grid --- .../src/app/components/navbar/common.ts | 19 +++++++++ .../components/navbar/submenu-grid.e2e.tsx | 16 ++++++++ .../Navbar-Submenu-Grid-Open--state.png | Bin 0 -> 26594 bytes ...beyond-the-visible-grid-rows--scrolled.png | Bin 0 -> 26655 bytes .../components/navbar/navbar.e2e.spec.ts | 37 ++++++++++++++++++ 5 files changed, 72 insertions(+) create mode 100644 e2e/nextjs-app/src/app/components/navbar/submenu-grid.e2e.tsx create mode 100644 e2e/tests/components/navbar/__screenshots__/chromium/Navbar-Submenu-Grid-Open--state.png create mode 100644 e2e/tests/components/navbar/__screenshots__/chromium/Navbar-Submenu-Grid-Scroll-reveals-items-beyond-the-visible-grid-rows--scrolled.png diff --git a/e2e/nextjs-app/src/app/components/navbar/common.ts b/e2e/nextjs-app/src/app/components/navbar/common.ts index 1e8936f1a1..d3b2b9a622 100644 --- a/e2e/nextjs-app/src/app/components/navbar/common.ts +++ b/e2e/nextjs-app/src/app/components/navbar/common.ts @@ -72,3 +72,22 @@ export const navItemsWithSubmenu: NavItemProps[] = [ ], }, ]; + +export const navItemsWithSubmenuGrid: NavItemProps[] = [ + { + id: "home", + children: "Home", + href: "https://www.life.gov.sg", + }, + { + id: "guides", + children: "Guides", + subMenuColumns: 3, + subMenuRows: 3, + subMenu: Array.from({ length: 12 }, (_, index) => ({ + id: `guides-item-${index + 1}`, + children: `Guides item ${index + 1}`, + href: "https://www.life.gov.sg", + })), + }, +]; diff --git a/e2e/nextjs-app/src/app/components/navbar/submenu-grid.e2e.tsx b/e2e/nextjs-app/src/app/components/navbar/submenu-grid.e2e.tsx new file mode 100644 index 0000000000..0ea9f12c82 --- /dev/null +++ b/e2e/nextjs-app/src/app/components/navbar/submenu-grid.e2e.tsx @@ -0,0 +1,16 @@ +"use client"; + +import { Navbar } from "@lifesg/react-design-system/navbar"; + +import { downloadActionButtons, navItemsWithSubmenuGrid } from "./common"; + +export default function Story() { + return ( + {}} + /> + ); +} diff --git a/e2e/tests/components/navbar/__screenshots__/chromium/Navbar-Submenu-Grid-Open--state.png b/e2e/tests/components/navbar/__screenshots__/chromium/Navbar-Submenu-Grid-Open--state.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd1151ad17716d4bf99c883084e51052f827c9d GIT binary patch literal 26594 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 literal 0 HcmV?d00001 diff --git a/e2e/tests/components/navbar/navbar.e2e.spec.ts b/e2e/tests/components/navbar/navbar.e2e.spec.ts index 7014ad3f6b..44f2a5a086 100644 --- a/e2e/tests/components/navbar/navbar.e2e.spec.ts +++ b/e2e/tests/components/navbar/navbar.e2e.spec.ts @@ -22,6 +22,7 @@ class StoryPage extends AbstractStoryPage { mobileMenuButton: Locator; servicesTrigger: Locator; appTrigger: Locator; + guidesTrigger: Locator; servicesMobileTrigger: Locator; closeButton: Locator; drawer: Locator; @@ -47,6 +48,7 @@ class StoryPage extends AbstractStoryPage { mobileMenuButton: page.getByTestId("button__mobile-menu"), servicesTrigger: page.getByRole("button", { name: "Services" }), appTrigger: page.getByRole("button", { name: "LifeSG app" }), + guidesTrigger: page.getByRole("button", { name: "Guides" }), servicesMobileTrigger: page.getByTestId( "link__mobile-2-expand-collapse-button" ), @@ -257,6 +259,41 @@ test.describe("Navbar", () => { }); }); + test.describe("Submenu Grid", () => { + test.beforeEach(async ({ story }) => { + await story.init("submenu-grid"); + }); + + test("Open", async ({ story }) => { + await story.locators.internal.guidesTrigger.click(); + await compareScreenshot(story, "state", { + fullscreen: true, + }); + }); + + test("Scroll reveals items beyond the visible grid rows", async ({ + story, + }) => { + await story.locators.internal.guidesTrigger.click(); + + const overflowItem = + story.locators.internal.submenuLink("Guides item 12"); + + await test.step("Overflow item starts outside the visible grid area", async () => { + await expect(overflowItem).not.toBeInViewport(); + }); + + await test.step("Scrolling brings the overflow item into view", async () => { + await overflowItem.scrollIntoViewIfNeeded(); + await expect(overflowItem).toBeInViewport(); + + await compareScreenshot(story, "scrolled", { + fullscreen: true, + }); + }); + }); + }); + test.describe("Mobile", () => { test.describe(() => { test.beforeEach(async ({ story }) => { From 30e0530eb6ccf7effc37aa5a5dd8534e5481f200 Mon Sep 17 00:00:00 2001 From: Fauzan Naufan Date: Thu, 17 Sep 2026 10:49:07 +0800 Subject: [PATCH 7/8] [MOL-22399][FN] fix: address code review findings --- src/menu/types.ts | 2 +- src/navbar/submenu-grid.tsx | 15 +++++++++++---- src/navbar/types.ts | 4 ++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/menu/types.ts b/src/menu/types.ts index 92d7c3320a..3b9b216c7b 100644 --- a/src/menu/types.ts +++ b/src/menu/types.ts @@ -48,7 +48,7 @@ export interface MenuContentProps /** * Maximum width of the panel in pixels. * - * @default 384 (24rem) + * @default 24rem (384px at the default 16px root font size) */ maxWidth?: number | undefined; } diff --git a/src/navbar/submenu-grid.tsx b/src/navbar/submenu-grid.tsx index 617f296376..74d2401f59 100644 --- a/src/navbar/submenu-grid.tsx +++ b/src/navbar/submenu-grid.tsx @@ -41,11 +41,14 @@ export const SubMenuGrid = ({ items, columns, rows, -}: Props): JSX.Element => { +}: Props): JSX.Element | null => { // ========================================================================= // CONST, STATE, REF // ========================================================================= const wrapperRef = useRef(null); + // ResizeObserver target: the actual grid
        , not wrapperRef — wrapperRef + // is `display: contents` and never reports a real size to observe. + const gridRef = useRef(null); // ========================================================================= // HELPER FUNCTIONS @@ -54,6 +57,7 @@ export const SubMenuGrid = ({ const grid = wrapperRef.current ?.firstElementChild as HTMLElement | null; if (!grid) return; + gridRef.current = grid; grid.style.setProperty(styles.tokens.grid.columns, String(columns)); @@ -68,7 +72,7 @@ export const SubMenuGrid = ({ | undefined; if (!boundaryItem) return; - const rowGap = parseFloat(getComputedStyle(grid).rowGap || "0"); + const rowGap = parseFloat(getComputedStyle(grid).rowGap) || 0; const visibleHeight = boundaryItem.offsetTop - rowGap / 2; grid.style.setProperty( @@ -83,20 +87,23 @@ export const SubMenuGrid = ({ useIsomorphicLayoutEffect(() => { applyGridLayout(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [items.length, columns, rows]); + }, [items, columns, rows]); useResizeDetector({ handleWidth: true, handleHeight: false, skipOnMount: true, refreshMode: "throttle", - targetRef: wrapperRef, + targetRef: gridRef, onResize: applyGridLayout, }); // ========================================================================= // RENDER FUNCTIONS // ========================================================================= + // guards against callers bypassing navbar-items.tsx's own columns/rows check + if (columns < 1 || rows < 1) return null; + return (
        diff --git a/src/navbar/types.ts b/src/navbar/types.ts index 55faa1bf6f..0d9bbf0a41 100644 --- a/src/navbar/types.ts +++ b/src/navbar/types.ts @@ -18,11 +18,15 @@ export interface NavItemLinkProps extends NavItemCommonProps { * * Has no effect on the mobile drawer, which always renders `subMenu` as * an expandable vertical list. + * + * @minimum 1 */ subMenuColumns?: number | undefined; /** * Number of grid rows of `subMenu` visible on desktop before the panel * becomes vertically scrollable. See `subMenuColumns`. + * + * @minimum 1 */ subMenuRows?: number | undefined; } From feef61f8622198b0777c8d51c833f025ec2296c3 Mon Sep 17 00:00:00 2001 From: Fauzan Naufan Date: Thu, 17 Sep 2026 10:56:01 +0800 Subject: [PATCH 8/8] [MOL-22399][FN] test: address testing gaps findings --- .../components/navbar/navbar.e2e.spec.ts | 24 ++++++++++++++++- tests/menu/menu.spec.tsx | 27 +++++++++++++++++++ tests/navbar/navbar.spec.tsx | 19 ++++++++++++- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/e2e/tests/components/navbar/navbar.e2e.spec.ts b/e2e/tests/components/navbar/navbar.e2e.spec.ts index 44f2a5a086..50abedb5dd 100644 --- a/e2e/tests/components/navbar/navbar.e2e.spec.ts +++ b/e2e/tests/components/navbar/navbar.e2e.spec.ts @@ -63,7 +63,8 @@ class StoryPage extends AbstractStoryPage { page.getByTestId(`menu__mobile-${index}`), mobileNavLink: (index: number) => page.getByTestId(`link__mobile-${index}`), - submenuLink: (name: string) => page.getByRole("link", { name }), + submenuLink: (name: string) => + page.getByRole("link", { name, exact: true }), downloadButton: page.getByTestId("action-button__download"), mobileNav: page.getByRole("navigation", { name: "Mobile navigation menu", @@ -292,6 +293,27 @@ test.describe("Navbar", () => { }); }); }); + + test("Keyboard users can Tab through grid items in order", async ({ + story, + }) => { + await story.locators.internal.guidesTrigger.click(); + + await story.page.keyboard.press("Tab"); + await expect( + story.locators.internal.submenuLink("Guides item 1") + ).toBeFocused(); + + await story.page.keyboard.press("Tab"); + await expect( + story.locators.internal.submenuLink("Guides item 2") + ).toBeFocused(); + + await story.page.keyboard.press("Tab"); + await expect( + story.locators.internal.submenuLink("Guides item 3") + ).toBeFocused(); + }); }); test.describe("Mobile", () => { diff --git a/tests/menu/menu.spec.tsx b/tests/menu/menu.spec.tsx index f831d8a0c7..7541e4f7fc 100644 --- a/tests/menu/menu.spec.tsx +++ b/tests/menu/menu.spec.tsx @@ -2,6 +2,7 @@ import "@testing-library/jest-dom"; import { fireEvent, render, screen } from "@testing-library/react"; import { Menu } from "src/menu"; +import { tokens as menuContentTokens } from "src/menu/menu-content.styles"; describe("Menu", () => { describe("Menu.Content", () => { @@ -16,6 +17,32 @@ describe("Menu", () => { expect(screen.getByText("hello")).toBeInTheDocument(); }); + it("should set the maxWidth CSS variable on the panel when provided", () => { + render( + +
        hello
        +
        + ); + + const panel = screen.getByTestId("menu-content"); + expect( + panel.style.getPropertyValue(menuContentTokens.panel.maxWidth) + ).toBe("640px"); + }); + + it("should not set the maxWidth CSS variable when not provided", () => { + render( + +
        hello
        +
        + ); + + const panel = screen.getByTestId("menu-content"); + expect( + panel.style.getPropertyValue(menuContentTokens.panel.maxWidth) + ).toBe(""); + }); + it("should move focus forward with ArrowDown", () => { render( diff --git a/tests/navbar/navbar.spec.tsx b/tests/navbar/navbar.spec.tsx index 54684b6f48..46280489ef 100644 --- a/tests/navbar/navbar.spec.tsx +++ b/tests/navbar/navbar.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from "@testing-library/react"; +import { fireEvent, render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { Navbar } from "src/navbar"; import * as subMenuGridStyles from "src/navbar/submenu-grid.styles"; @@ -229,6 +229,23 @@ describe("Navbar", () => { document.body.getElementsByClassName(subMenuGridStyles.grid) ).toHaveLength(0); }); + + it("should move focus between grid items with ArrowDown", async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByTestId("link__1")); + + const panel = screen.getByTestId("menu-content"); + const first = screen.getByRole("link", { name: "Sub item 1" }); + const second = screen.getByRole("link", { name: "Sub item 2" }); + + first.focus(); + expect(first).toHaveFocus(); + + fireEvent.keyDown(panel, { key: "ArrowDown" }); + expect(second).toHaveFocus(); + }); }); });