Skip to content

Commit a044a08

Browse files
authored
fix(ui): ensures modal closes on route change (#14718)
### What? Adds fix to ensure modal fully closes when navigating back/forwards in the browser. ### Why? Currently if the modal is open and you navigate back in the browser, the modal content gets closed but the container remains visible, due to this you cannot click anything in the admin panel. ### How? Adds a `closeModalOnRouteChange` component as described in the `faceless-ui` package [here](https://facelessui.com/docs/modal/routing#closemodalonroutechange). #### NOTE We have a similar PR [here](#14721). Decided to merge efforts into this one, tests moved from other PR into this one. Reported by client.
1 parent 42a4384 commit a044a08

File tree

6 files changed

+142
-0
lines changed

6 files changed

+142
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use client'
2+
3+
import { useModal } from '@faceless-ui/modal'
4+
import { usePathname } from 'next/navigation.js'
5+
import { useEffect, useRef } from 'react'
6+
7+
import { useEffectEvent } from '../../hooks/useEffectEvent.js'
8+
9+
export function CloseModalOnRouteChange() {
10+
const { closeAllModals } = useModal()
11+
const pathname = usePathname()
12+
13+
const closeAllModalsEffectEvent = useEffectEvent(() => {
14+
closeAllModals()
15+
})
16+
17+
const initialRenderRef = useRef(true)
18+
19+
useEffect(() => {
20+
if (initialRenderRef.current) {
21+
initialRenderRef.current = false
22+
return
23+
}
24+
25+
closeAllModalsEffectEvent()
26+
}, [pathname])
27+
28+
return null
29+
}

packages/ui/src/providers/Root/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import React from 'react'
1616

1717
import type { Theme } from '../Theme/index.js'
1818

19+
import { CloseModalOnRouteChange } from '../../elements/CloseModalOnRouteChange/index.js'
1920
import { LoadingOverlayProvider } from '../../elements/LoadingOverlay/index.js'
2021
import { NavProvider } from '../../elements/Nav/context.js'
2122
import { StayLoggedInModal } from '../../elements/StayLoggedIn/index.js'
@@ -101,6 +102,7 @@ export const RootProvider: React.FC<Props> = ({
101102
<ScrollInfoProvider>
102103
<SearchParamsProvider>
103104
<ModalProvider classPrefix="payload" transTime={0} zIndex="var(--z-modal)">
105+
<CloseModalOnRouteChange />
104106
<AuthProvider permissions={permissions} user={user}>
105107
<PreferencesProvider>
106108
<ThemeProvider theme={theme}>

test/admin-root/e2e.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,16 @@ test.describe('Admin Panel (Root)', () => {
145145
await page.goto(`${url.admin}/custom-view`)
146146
await expect(page.locator('#custom-view')).toBeVisible()
147147
})
148+
149+
test('should close modal on route change', async () => {
150+
await page.goto(url.create)
151+
const textField = page.locator('#field-text')
152+
await textField.fill('updated')
153+
await page.click('a[aria-label="Account"]')
154+
const modal = page.locator('div.payload__modal-container')
155+
await expect(modal).toBeVisible()
156+
157+
await page.goBack()
158+
await expect(modal).toBeHidden()
159+
})
148160
})

test/fields-relationship/collections/Relationship/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ export const Relationship: CollectionConfig = {
2323
],
2424
},
2525
fields: [
26+
{
27+
name: 'relationToSelf',
28+
relationTo: slug,
29+
type: 'relationship',
30+
},
2631
{
2732
name: 'relationship',
2833
relationTo: relationOneSlug,

test/fields-relationship/e2e.spec.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,70 @@ describe('Relationship Field', () => {
575575
await expect(documentDrawer).toBeVisible()
576576
})
577577

578+
test('should open document from drawer by clicking on ID Label', async () => {
579+
const relatedDoc = await payload.create({
580+
collection: relationOneSlug,
581+
data: {
582+
name: 'Drawer ID Label',
583+
},
584+
})
585+
const doc = await payload.create({
586+
collection: slug,
587+
data: {
588+
relationship: relatedDoc.id,
589+
},
590+
})
591+
await payload.update({
592+
collection: slug,
593+
id: doc.id,
594+
data: {
595+
relationToSelf: doc.id,
596+
},
597+
})
598+
599+
await page.goto(url.edit(doc.id))
600+
// open first drawer (self-relation)
601+
const selfRelationTrigger = page.locator(
602+
'#field-relationToSelf button.relationship--single-value__drawer-toggler',
603+
)
604+
await expect(selfRelationTrigger).toBeVisible()
605+
await selfRelationTrigger.click()
606+
607+
const drawer1 = page.locator('[id^=doc-drawer_fields-relationship_1_]')
608+
await expect(drawer1).toBeVisible()
609+
610+
// open nested drawer (relation field inside self-relation)
611+
const relationshipDrawerTrigger = drawer1.locator(
612+
'#field-relationship button.relationship--single-value__drawer-toggler',
613+
)
614+
await expect(relationshipDrawerTrigger).toBeVisible()
615+
await relationshipDrawerTrigger.click()
616+
617+
const drawer2 = page.locator('[id^=doc-drawer_relation-one_2_]')
618+
await expect(drawer2).toBeVisible()
619+
620+
const idLabel = drawer2.locator('.id-label')
621+
await expect(idLabel).toBeVisible()
622+
await idLabel.locator('a').click()
623+
624+
const closedModalLocator = page.locator(
625+
'.payload__modal-container.payload__modal-container--exitDone',
626+
)
627+
628+
await expect(closedModalLocator).toHaveCount(1)
629+
630+
await Promise.all([
631+
payload.delete({
632+
collection: relationOneSlug,
633+
id: relatedDoc.id,
634+
}),
635+
payload.delete({
636+
collection: slug,
637+
id: doc.id,
638+
}),
639+
])
640+
})
641+
578642
test('should open document drawer and append newly created docs onto the parent field', async () => {
579643
await page.goto(url.edit(docWithExistingRelations.id))
580644
await wait(300)

test/fields-relationship/payload-types.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export interface Config {
8181
podcasts: Podcast;
8282
'mixed-media': MixedMedia;
8383
'versioned-relationship-field': VersionedRelationshipField;
84+
'payload-kv': PayloadKv;
8485
users: User;
8586
'payload-locked-documents': PayloadLockedDocument;
8687
'payload-preferences': PayloadPreference;
@@ -102,6 +103,7 @@ export interface Config {
102103
podcasts: PodcastsSelect<false> | PodcastsSelect<true>;
103104
'mixed-media': MixedMediaSelect<false> | MixedMediaSelect<true>;
104105
'versioned-relationship-field': VersionedRelationshipFieldSelect<false> | VersionedRelationshipFieldSelect<true>;
106+
'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>;
105107
users: UsersSelect<false> | UsersSelect<true>;
106108
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
107109
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
@@ -110,6 +112,7 @@ export interface Config {
110112
db: {
111113
defaultIDType: string;
112114
};
115+
fallbackLocale: ('false' | 'none' | 'null') | false | null | 'en' | 'en'[];
113116
globals: {};
114117
globalsSelect: {};
115118
locale: 'en';
@@ -145,6 +148,7 @@ export interface UserAuthOperations {
145148
*/
146149
export interface FieldsRelationship {
147150
id: string;
151+
relationToSelf?: (string | null) | FieldsRelationship;
148152
relationship?: (string | null) | RelationOne;
149153
relationshipHasMany?: (string | RelationOne)[] | null;
150154
relationshipMultiple?:
@@ -381,6 +385,23 @@ export interface VersionedRelationshipField {
381385
createdAt: string;
382386
_status?: ('draft' | 'published') | null;
383387
}
388+
/**
389+
* This interface was referenced by `Config`'s JSON-Schema
390+
* via the `definition` "payload-kv".
391+
*/
392+
export interface PayloadKv {
393+
id: string;
394+
key: string;
395+
data:
396+
| {
397+
[k: string]: unknown;
398+
}
399+
| unknown[]
400+
| string
401+
| number
402+
| boolean
403+
| null;
404+
}
384405
/**
385406
* This interface was referenced by `Config`'s JSON-Schema
386407
* via the `definition` "users".
@@ -519,6 +540,7 @@ export interface PayloadMigration {
519540
* via the `definition` "fields-relationship_select".
520541
*/
521542
export interface FieldsRelationshipSelect<T extends boolean = true> {
543+
relationToSelf?: T;
522544
relationship?: T;
523545
relationshipHasMany?: T;
524546
relationshipMultiple?: T;
@@ -669,6 +691,14 @@ export interface VersionedRelationshipFieldSelect<T extends boolean = true> {
669691
createdAt?: T;
670692
_status?: T;
671693
}
694+
/**
695+
* This interface was referenced by `Config`'s JSON-Schema
696+
* via the `definition` "payload-kv_select".
697+
*/
698+
export interface PayloadKvSelect<T extends boolean = true> {
699+
key?: T;
700+
data?: T;
701+
}
672702
/**
673703
* This interface was referenced by `Config`'s JSON-Schema
674704
* via the `definition` "users_select".

0 commit comments

Comments
 (0)