diff --git a/packages/ui/src/elements/CloseModalOnRouteChange/index.tsx b/packages/ui/src/elements/CloseModalOnRouteChange/index.tsx new file mode 100644 index 00000000000..551ebecfd46 --- /dev/null +++ b/packages/ui/src/elements/CloseModalOnRouteChange/index.tsx @@ -0,0 +1,29 @@ +'use client' + +import { useModal } from '@faceless-ui/modal' +import { usePathname } from 'next/navigation.js' +import { useEffect, useRef } from 'react' + +import { useEffectEvent } from '../../hooks/useEffectEvent.js' + +export function CloseModalOnRouteChange() { + const { closeAllModals } = useModal() + const pathname = usePathname() + + const closeAllModalsEffectEvent = useEffectEvent(() => { + closeAllModals() + }) + + const initialRenderRef = useRef(true) + + useEffect(() => { + if (initialRenderRef.current) { + initialRenderRef.current = false + return + } + + closeAllModalsEffectEvent() + }, [pathname]) + + return null +} diff --git a/packages/ui/src/providers/Root/index.tsx b/packages/ui/src/providers/Root/index.tsx index c704b99533f..e514476516e 100644 --- a/packages/ui/src/providers/Root/index.tsx +++ b/packages/ui/src/providers/Root/index.tsx @@ -16,6 +16,7 @@ import React from 'react' import type { Theme } from '../Theme/index.js' +import { CloseModalOnRouteChange } from '../../elements/CloseModalOnRouteChange/index.js' import { LoadingOverlayProvider } from '../../elements/LoadingOverlay/index.js' import { NavProvider } from '../../elements/Nav/context.js' import { StayLoggedInModal } from '../../elements/StayLoggedIn/index.js' @@ -101,6 +102,7 @@ export const RootProvider: React.FC = ({ + diff --git a/test/admin-root/e2e.spec.ts b/test/admin-root/e2e.spec.ts index e8ff5d1ca65..f42be71c230 100644 --- a/test/admin-root/e2e.spec.ts +++ b/test/admin-root/e2e.spec.ts @@ -145,4 +145,16 @@ test.describe('Admin Panel (Root)', () => { await page.goto(`${url.admin}/custom-view`) await expect(page.locator('#custom-view')).toBeVisible() }) + + test('should close modal on route change', async () => { + await page.goto(url.create) + const textField = page.locator('#field-text') + await textField.fill('updated') + await page.click('a[aria-label="Account"]') + const modal = page.locator('div.payload__modal-container') + await expect(modal).toBeVisible() + + await page.goBack() + await expect(modal).toBeHidden() + }) }) diff --git a/test/fields-relationship/collections/Relationship/index.ts b/test/fields-relationship/collections/Relationship/index.ts index 532c1d0eb3d..20fc2c94ad7 100644 --- a/test/fields-relationship/collections/Relationship/index.ts +++ b/test/fields-relationship/collections/Relationship/index.ts @@ -23,6 +23,11 @@ export const Relationship: CollectionConfig = { ], }, fields: [ + { + name: 'relationToSelf', + relationTo: slug, + type: 'relationship', + }, { name: 'relationship', relationTo: relationOneSlug, diff --git a/test/fields-relationship/e2e.spec.ts b/test/fields-relationship/e2e.spec.ts index fc2403e62ab..13fbf9f2165 100644 --- a/test/fields-relationship/e2e.spec.ts +++ b/test/fields-relationship/e2e.spec.ts @@ -575,6 +575,70 @@ describe('Relationship Field', () => { await expect(documentDrawer).toBeVisible() }) + test('should open document from drawer by clicking on ID Label', async () => { + const relatedDoc = await payload.create({ + collection: relationOneSlug, + data: { + name: 'Drawer ID Label', + }, + }) + const doc = await payload.create({ + collection: slug, + data: { + relationship: relatedDoc.id, + }, + }) + await payload.update({ + collection: slug, + id: doc.id, + data: { + relationToSelf: doc.id, + }, + }) + + await page.goto(url.edit(doc.id)) + // open first drawer (self-relation) + const selfRelationTrigger = page.locator( + '#field-relationToSelf button.relationship--single-value__drawer-toggler', + ) + await expect(selfRelationTrigger).toBeVisible() + await selfRelationTrigger.click() + + const drawer1 = page.locator('[id^=doc-drawer_fields-relationship_1_]') + await expect(drawer1).toBeVisible() + + // open nested drawer (relation field inside self-relation) + const relationshipDrawerTrigger = drawer1.locator( + '#field-relationship button.relationship--single-value__drawer-toggler', + ) + await expect(relationshipDrawerTrigger).toBeVisible() + await relationshipDrawerTrigger.click() + + const drawer2 = page.locator('[id^=doc-drawer_relation-one_2_]') + await expect(drawer2).toBeVisible() + + const idLabel = drawer2.locator('.id-label') + await expect(idLabel).toBeVisible() + await idLabel.locator('a').click() + + const closedModalLocator = page.locator( + '.payload__modal-container.payload__modal-container--exitDone', + ) + + await expect(closedModalLocator).toHaveCount(1) + + await Promise.all([ + payload.delete({ + collection: relationOneSlug, + id: relatedDoc.id, + }), + payload.delete({ + collection: slug, + id: doc.id, + }), + ]) + }) + test('should open document drawer and append newly created docs onto the parent field', async () => { await page.goto(url.edit(docWithExistingRelations.id)) await wait(300) diff --git a/test/fields-relationship/payload-types.ts b/test/fields-relationship/payload-types.ts index 49232c20aa6..4f743e1d295 100644 --- a/test/fields-relationship/payload-types.ts +++ b/test/fields-relationship/payload-types.ts @@ -81,6 +81,7 @@ export interface Config { podcasts: Podcast; 'mixed-media': MixedMedia; 'versioned-relationship-field': VersionedRelationshipField; + 'payload-kv': PayloadKv; users: User; 'payload-locked-documents': PayloadLockedDocument; 'payload-preferences': PayloadPreference; @@ -102,6 +103,7 @@ export interface Config { podcasts: PodcastsSelect | PodcastsSelect; 'mixed-media': MixedMediaSelect | MixedMediaSelect; 'versioned-relationship-field': VersionedRelationshipFieldSelect | VersionedRelationshipFieldSelect; + 'payload-kv': PayloadKvSelect | PayloadKvSelect; users: UsersSelect | UsersSelect; 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; 'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect; @@ -110,6 +112,7 @@ export interface Config { db: { defaultIDType: string; }; + fallbackLocale: ('false' | 'none' | 'null') | false | null | 'en' | 'en'[]; globals: {}; globalsSelect: {}; locale: 'en'; @@ -145,6 +148,7 @@ export interface UserAuthOperations { */ export interface FieldsRelationship { id: string; + relationToSelf?: (string | null) | FieldsRelationship; relationship?: (string | null) | RelationOne; relationshipHasMany?: (string | RelationOne)[] | null; relationshipMultiple?: @@ -381,6 +385,23 @@ export interface VersionedRelationshipField { createdAt: string; _status?: ('draft' | 'published') | null; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-kv". + */ +export interface PayloadKv { + id: string; + key: string; + data: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "users". @@ -519,6 +540,7 @@ export interface PayloadMigration { * via the `definition` "fields-relationship_select". */ export interface FieldsRelationshipSelect { + relationToSelf?: T; relationship?: T; relationshipHasMany?: T; relationshipMultiple?: T; @@ -669,6 +691,14 @@ export interface VersionedRelationshipFieldSelect { createdAt?: T; _status?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-kv_select". + */ +export interface PayloadKvSelect { + key?: T; + data?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "users_select".