fix(next): incorrect active state for partial matches of collection names in sidebar (#11511)

Previously, collections with similar names (e.g., `uploads` and
`uploads-poly`) both appeared active when viewing either collection.

This was due to `pathname.startsWith(href)`, which caused partial
matches.

This update refines the `isActive` logic to prevent partial matches.
This commit is contained in:
Patrik
2025-03-03 16:46:47 -05:00
committed by GitHub
parent c417e3a627
commit 7d2480aef9
6 changed files with 88 additions and 1 deletions

View File

@@ -44,7 +44,8 @@ export const DefaultNavClient: React.FC<{
id = `nav-global-${slug}`
}
const isActive = pathname.startsWith(href)
const isActive =
pathname.startsWith(href) && ['/', undefined].includes(pathname[href.length])
const Label = (
<>

View File

@@ -0,0 +1,14 @@
import type { CollectionConfig } from 'payload'
import { uploadTwoCollectionSlug } from '../slugs.js'
export const UploadTwoCollection: CollectionConfig = {
slug: uploadTwoCollectionSlug,
fields: [
{
name: 'title',
type: 'text',
},
],
upload: true,
}

View File

@@ -19,6 +19,7 @@ import { CollectionNoApiView } from './collections/NoApiView.js'
import { CollectionNotInView } from './collections/NotInView.js'
import { Posts } from './collections/Posts.js'
import { UploadCollection } from './collections/Upload.js'
import { UploadTwoCollection } from './collections/UploadTwo.js'
import { Users } from './collections/Users.js'
import { with300Documents } from './collections/With300Documents.js'
import { CustomGlobalViews1 } from './globals/CustomViews1.js'
@@ -40,6 +41,7 @@ import {
protectedCustomNestedViewPath,
publicCustomViewPath,
} from './shared.js'
export default buildConfigWithDefaults({
admin: {
importMap: {
@@ -141,6 +143,7 @@ export default buildConfigWithDefaults({
},
collections: [
UploadCollection,
UploadTwoCollection,
Posts,
Users,
CollectionHidden,

View File

@@ -39,6 +39,7 @@ import {
notInViewCollectionSlug,
postsCollectionSlug,
settingsGlobalSlug,
uploadTwoCollectionSlug,
} from '../../slugs.js'
const { beforeAll, beforeEach, describe } = test
@@ -73,6 +74,7 @@ describe('General', () => {
let disableDuplicateURL: AdminUrlUtil
let serverURL: string
let adminRoutes: ReturnType<typeof getRoutes>
let uploadsTwo: AdminUrlUtil
beforeAll(async ({ browser }, testInfo) => {
const prebuild = false // Boolean(process.env.CI)
@@ -90,6 +92,7 @@ describe('General', () => {
globalURL = new AdminUrlUtil(serverURL, globalSlug)
customViewsURL = new AdminUrlUtil(serverURL, customViews2CollectionSlug)
disableDuplicateURL = new AdminUrlUtil(serverURL, disableDuplicateSlug)
uploadsTwo = new AdminUrlUtil(serverURL, uploadTwoCollectionSlug)
context = await browser.newContext()
page = await context.newPage()
@@ -424,6 +427,27 @@ describe('General', () => {
expect(tagName).toBe('a')
})
test('should only have one nav item active at a time', async () => {
await page.goto(uploadsTwo.list)
await openNav(page)
// Locate "uploads" and "uploads-two" nav items
const uploadsNavItem = page.locator('.nav-group__content #nav-uploads')
const uploadsTwoNavItem = page.locator('.nav-group__content #nav-uploads-two')
// Ensure both exist before continuing
await expect(uploadsNavItem).toBeVisible()
await expect(uploadsTwoNavItem).toBeVisible()
// Locate all nav items containing the nav__link-indicator
const activeNavItems = page.locator(
'.nav-group__content .nav__link:has(.nav__link-indicator), .nav-group__content div.nav__link:has(.nav__link-indicator)',
)
// Expect exactly one nav item to have the indicator
await expect(activeNavItems).toHaveCount(1)
})
test('breadcrumbs — should navigate from list to dashboard', async () => {
await page.goto(postsUrl.list)
await page.locator(`.step-nav a[href="${adminRoutes.routes.admin}"]`).click()

View File

@@ -66,6 +66,7 @@ export interface Config {
};
collections: {
uploads: Upload;
'uploads-two': UploadsTwo;
posts: Post;
users: User;
'hidden-collection': HiddenCollection;
@@ -90,6 +91,7 @@ export interface Config {
collectionsJoins: {};
collectionsSelect: {
uploads: UploadsSelect<false> | UploadsSelect<true>;
'uploads-two': UploadsTwoSelect<false> | UploadsTwoSelect<true>;
posts: PostsSelect<false> | PostsSelect<true>;
users: UsersSelect<false> | UsersSelect<true>;
'hidden-collection': HiddenCollectionSelect<false> | HiddenCollectionSelect<true>;
@@ -192,6 +194,25 @@ export interface Upload {
};
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "uploads-two".
*/
export interface UploadsTwo {
id: string;
title?: string | null;
updatedAt: string;
createdAt: string;
url?: string | null;
thumbnailURL?: string | null;
filename?: string | null;
mimeType?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This is a custom collection description.
*
@@ -469,6 +490,10 @@ export interface PayloadLockedDocument {
relationTo: 'uploads';
value: string | Upload;
} | null)
| ({
relationTo: 'uploads-two';
value: string | UploadsTwo;
} | null)
| ({
relationTo: 'posts';
value: string | Post;
@@ -611,6 +636,24 @@ export interface UploadsSelect<T extends boolean = true> {
};
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "uploads-two_select".
*/
export interface UploadsTwoSelect<T extends boolean = true> {
title?: T;
updatedAt?: T;
createdAt?: T;
url?: T;
thumbnailURL?: T;
filename?: T;
mimeType?: T;
filesize?: T;
width?: T;
height?: T;
focalX?: T;
focalY?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "posts_select".

View File

@@ -12,6 +12,8 @@ export const notInViewCollectionSlug = 'not-in-view-collection'
export const noApiViewCollectionSlug = 'collection-no-api-view'
export const disableDuplicateSlug = 'disable-duplicate'
export const uploadCollectionSlug = 'uploads'
export const uploadTwoCollectionSlug = 'uploads-two'
export const customFieldsSlug = 'custom-fields'
export const listDrawerSlug = 'with-list-drawer'